#include "globals.h"
#ifdef READER_VIACCESS
#include "oscam-aes.h"
#include "oscam-time.h"
#include "oscam-emm.h"
#include "reader-common.h"
#include "cscrypt/des.h"
#include "oscam-work.h"

struct geo_cache
{
	uint32_t provid;
	uint8_t geo[256];
	uint8_t geo_len;
	int32_t number_ecm;
};

struct viaccess_data
{
	struct geo_cache last_geo;
	uint8_t availkeys[CS_MAXPROV][16];
};

struct via_date
{
	uint16_t day_s   : 5;
	uint16_t month_s : 4;
	uint16_t year_s  : 7;

	uint16_t day_e   : 5;
	uint16_t month_e : 4;
	uint16_t year_e  : 7;
};

static void parse_via_date(const uint8_t *buf, struct via_date *vd, int32_t fend)
{
	uint16_t date;

	date = (buf[0] << 8) | buf[1];
	vd->day_s   = date & 0x1f;
	vd->month_s = (date >> 5) & 0x0f;
	vd->year_s  = (date >> 9) & 0x7f;

	if(fend)
	{
		date = (buf[2] << 8) | buf[3];
		vd->day_e   = date & 0x1f;
		vd->month_e = (date >> 5) & 0x0f;
		vd->year_e  = (date >> 9) & 0x7f;
	}
}

struct emm_rass *find_rabuf(struct s_client *client, int32_t provid, uint8_t nano, int8_t add)
{
	struct emm_rass *e;
	LL_ITER it;

	if(!client->ra_buf)
	{
		client->ra_buf = ll_create("client->ra_buf");
	}

	it = ll_iter_create(client->ra_buf);

	while((e = ll_iter_next(&it)) != NULL)
	{
		if(!add && e->provid == provid && e->emmlen != 0)
		{
			return e;
		}

		if(add && e->provid == provid && e->emm[0] == nano)
		{
			return e;
		}
	}

	if(!add)
	{
		return NULL;
	}

	if(!cs_malloc(&e, sizeof(struct emm_rass)))
	{
		return NULL;
	}

	e->provid = provid;
	ll_append(client->ra_buf, e);
	return e;
}

static void show_class(struct s_reader *reader, const char *p, uint32_t provid, const uint8_t *b, int32_t l)
{
	int32_t i, j;

	// b -> via date (4 uint8_ts)
	b += 4;
	l -= 4;

	j = l - 1;
	for(; j >= 0; j--)
	{
		for(i = 0; i < 8; i++)
		{
			if(b[j] & (1 << (i & 7)))
			{
				uint8_t cls;
				struct via_date vd;
				parse_via_date(b - 4, &vd, 1);
				cls = (l - (j + 1)) * 8 + i;

				if(p) // just show class info, dont add entitlement!
				{
					rdr_log(reader, "%sclass: %02X, expiry date: %04d/%02d/%02d - %04d/%02d/%02d", p, cls,
							vd.year_s + 1980, vd.month_s, vd.day_s,
							vd.year_e + 1980, vd.month_e, vd.day_e);
				}
				else
				{
					rdr_log(reader, "class: %02X, expiry date: %04d/%02d/%02d - %04d/%02d/%02d", cls,
							vd.year_s + 1980, vd.month_s, vd.day_s,
							vd.year_e + 1980, vd.month_e, vd.day_e);

					//convert time:
					time_t start_t, end_t;
					struct tm tm;
					memset(&tm, 0, sizeof(tm));
					tm.tm_year = vd.year_s + 80; //via year starts in 1980, tm_year starts in 1900
					tm.tm_mon = vd.month_s - 1; // january is 0 in tm_mon
					tm.tm_mday = vd.day_s;
					start_t = cs_timegm(&tm);

					tm.tm_year = vd.year_e + 80; //via year starts in 1980, tm_year starts in 1900
					tm.tm_mon = vd.month_e - 1; // january is 0 in tm_mon
					tm.tm_mday = vd.day_e;
					end_t = cs_timegm(&tm);
					cs_add_entitlement(reader, reader->caid, provid, cls, cls, start_t, end_t, 5, 1);
				}
			}
		}
	}
}

static int8_t add_find_class(struct s_reader *reader, uint32_t provid, const uint8_t *b, int32_t l, int8_t add)
{
	int32_t i, j, freshdate = 0;

	// b -> via date (4 uint8_ts)
	b += 4;
	l -= 4;

	j = l - 1;
	for(; j >= 0; j--)
	{
		for(i = 0; i < 8; i++)
		{
			if(b[j] & (1 << (i & 7)))
			{
				uint8_t cls;
				cls = (l - (j + 1)) * 8 + i;
				if(cs_add_entitlement(reader, reader->caid, provid, cls, cls, 0, 0, 5, 0) == NULL && !add)
				{
					rdr_log(reader, "provid %06X class %02X not found", provid, cls);
					freshdate = 1;
				}
				else
				{
					if(!add)
					{
						rdr_log(reader, "provid %06X has matching class %02X", provid, cls);
					}
					struct via_date vd;
					parse_via_date(b - 4, &vd, 1);
					time_t start_t, end_t;
					struct tm tm;

					//convert time:
					memset(&tm, 0, sizeof(tm));
					tm.tm_year = vd.year_s + 80; // via year starts in 1980, tm_year starts in 1900
					tm.tm_mon = vd.month_s - 1; // january is 0 in tm_mon
					tm.tm_mday = vd.day_s;
					start_t = cs_timegm(&tm);

					tm.tm_year = vd.year_e + 80; // via year starts in 1980, tm_year starts in 1900
					tm.tm_mon = vd.month_e - 1; // january is 0 in tm_mon
					tm.tm_mday = vd.day_e;
					end_t = cs_timegm(&tm);

					if(cs_add_entitlement(reader, reader->caid, provid, cls, cls, start_t, end_t, 5, add) != NULL)
					{
						if(!add)
						{
							rdr_log(reader, "class %02X provid %06X has already this daterange or newer entitled", cls, provid);
						}
					}
					else
					{
						freshdate = 1;
					}
				}
			}
		}
	}

	if(freshdate == 0)
	{
		return -2;
	}

	return 1; // emmdate is fresh!
}

static void show_subs(struct s_reader *reader, const uint8_t *emm)
{
	switch(emm[0])
	{
		case 0xA9:
		{
			show_class(reader, "nano A9: ", 1, emm + 2, emm[1]);
			break;
		}

		case 0xA6:
		{
			char szGeo[256];

			memset(szGeo, 0, 256);
			cs_strncpy(szGeo, (char *)emm + 2, emm[1]);
			rdr_log(reader, "nano A6: geo %s", szGeo);
			break;
		}

		case 0xB6:
		{
			uint8_t m; // modexp
			struct via_date vd;

			m = emm[emm[1] + 1];
			parse_via_date(emm + 2, &vd, 0);
			rdr_log(reader, "nano B6: modexp %d%d%d%d%d%d: %02d/%02d/%04d",
				(m & 0x20) ? 1 : 0,
				(m & 0x10) ? 1 : 0,
				(m & 0x08) ? 1 : 0,
				(m & 0x04) ? 1 : 0,
				(m & 0x02) ? 1 : 0,
				(m & 0x01) ? 1 : 0,
				vd.day_s, vd.month_s, vd.year_s + 1980);
			break;
		}
	}
}

static int32_t chk_prov(struct s_reader *reader, uint8_t *id, uint8_t keynr)
{
	struct viaccess_data *csystem_data = reader->csystem_data;
	int32_t i, j, rc;

	for(rc = i = 0; (!rc) && (i < reader->nprov); i++)
	{
		if(!memcmp(&reader->prid[i][1], id, 3))
		{
			for(j = 0; (!rc) && (j < 16); j++)
			{
				if(csystem_data->availkeys[i][j] == keynr)
				{
					rc = 1;
				}
			}
		}
	}
	return (rc);
}

static int32_t get_maturity(struct s_reader *reader)
{
	/* retrieve maturity rating on the card */

	def_resp;

	uint8_t insac[] = { 0xca, 0xac, 0x00, 0x00, 0x00 }; // select data
	uint8_t insb8[] = { 0xca, 0xb8, 0x00, 0x00, 0x00 }; // read selected data

	insac[2] = 0x06;
	write_cmd(insac, NULL); // request maturity rating

	insb8[4] = 0x02;
	write_cmd(insb8, NULL); // read maturity rating nano + len

	insb8[4] = cta_res[1];
	write_cmd(insb8, NULL); // read maturity rating

	reader->maturity = cta_res[cta_lr - 3] & 0x0F;
	if (reader->maturity < 0xF)
	{
		rdr_log(reader, "Maturity level [%X]= older than %i years", reader->maturity, reader->maturity);
	}
	else
	{
		rdr_log(reader, "Maturity level [%X]=no age limit", reader->maturity);
	}
	return 0;
}

static int32_t unlock_parental(struct s_reader *reader)
{
	/* disabling parental lock. assuming pin "0000" if no pin code is provided in the config */

	static const uint8_t inDPL[] = { 0xca, 0x24, 0x02, 0x00, 0x09 };
	uint8_t cmDPL[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F };
	def_resp;

	if(strcmp(reader->pincode, "none"))
	{
		rdr_log(reader, "Using PIN %s", reader->pincode);
		// the pin need to be coded in bcd, so we need to convert from ascii to bcd, so '1234' -> 0x12 0x34
		cmDPL[6] = ((reader->pincode[0] - 0x30) << 4) | ((reader->pincode[1] - 0x30) & 0x0f);
		cmDPL[7] = ((reader->pincode[2] - 0x30) << 4) | ((reader->pincode[3] - 0x30) & 0x0f);
	}
	else
	{
		rdr_log(reader, "Using PIN 0000!");
	}
	write_cmd(inDPL, cmDPL);

	if(!(cta_res[cta_lr - 2] == 0x90 && cta_res[cta_lr - 1] == 0))
	{
		if(strcmp(reader->pincode, "none"))
		{
			rdr_log(reader, "Can't disable parental lock. Wrong PIN? OSCam used %s!", reader->pincode);
		}
		else
		{
			rdr_log(reader, "Can't disable parental lock. Wrong PIN? OSCam used 0000!");
		}
	}
	else
	{
		rdr_log(reader, "Parental lock disabled");
		get_maturity(reader);
	}

	return 0;
}

int32_t hdSurEncBasicCrypt_D2_0F_11(int32_t Value, int32_t XorVal)
{
	int32_t i = (Value << 13) - Value + 0x1B59;
	i = (i * Value) + 0x07CF;
	return (i ^ XorVal);
}

int32_t hdSurEncCryptLookup_D2_0F_11(uint8_t Value, uint8_t AddrInd)
{
	static const uint8_t lookup[] =
	{
		0x94, 0xB2, 0xA9, 0x79, 0xC4, 0xC7, 0x0D, 0x36, 0x6F, 0x24, 0x11, 0xD1, 0xDB, 0x59, 0xD2, 0xA5,
		0xE1, 0x00, 0xD4, 0x97, 0xA3, 0x2B, 0x11, 0xFA, 0x5F, 0xF1, 0xC1, 0x44, 0xBF, 0x9B, 0x5A, 0xC8,
		0xF1, 0xE1, 0x99, 0x82, 0x0E, 0xB2, 0x01, 0x09, 0x0C, 0xC8, 0xB3, 0x3B, 0xD1, 0x80, 0x50, 0xE8,
		0xF5, 0x52, 0x4C, 0xE6, 0x82, 0xAC, 0x58, 0x40, 0xD4, 0x71, 0x87, 0x52, 0x06, 0xEA, 0xA6, 0x27,
		0xB7, 0xFE, 0x6C, 0x49, 0x47, 0x3B, 0x70, 0x6C, 0xEB, 0xCD, 0xC5, 0x0B, 0x8C, 0x31, 0x29, 0x42,
		0x4E, 0x10, 0x2B, 0x2D, 0x46, 0xEC, 0x39, 0xA3, 0x90, 0x4B, 0x25, 0x60, 0x9C, 0x62, 0xD4, 0x20,
		0xF6, 0x16, 0xA8, 0x9C, 0xE4, 0x20, 0xED, 0xC7, 0xBA, 0x5E, 0xB6, 0x4E, 0x03, 0x15, 0xA6, 0xF6,
		0x23, 0x98, 0x32, 0xC0, 0xAE, 0xA3, 0xFD, 0xD3, 0x7F, 0xF8, 0xED, 0xF0, 0x29, 0x29, 0x12, 0xB3,
		0xB7, 0x58, 0xAD, 0xA2, 0x58, 0x2C, 0x70, 0x1B, 0xA4, 0x25, 0xE8, 0xA5, 0x43, 0xF1, 0xB9, 0x8F,
		0x1E, 0x3B, 0x10, 0xDF, 0x52, 0xFE, 0x58, 0x29, 0xAD, 0x3F, 0x99, 0x4D, 0xDF, 0xD2, 0x08, 0x06,
		0xA1, 0x1C, 0x66, 0x29, 0x26, 0x80, 0x52, 0x8A, 0x5A, 0x73, 0xE7, 0xDF, 0xC1, 0xC4, 0x47, 0x82,
		0xAB, 0x5C, 0x32, 0xAE, 0x96, 0x04, 0x2B, 0xC3, 0x2D, 0x5A, 0xD2, 0xB0, 0x64, 0x88, 0x97, 0xBF,
		0x7E, 0x99, 0x60, 0xCC, 0x63, 0x76, 0x66, 0xE9, 0x9A, 0x3D, 0xBB, 0xF7, 0x7F, 0xE4, 0x7C, 0x3F,
		0xB8, 0x4D, 0x10, 0x8D, 0x2A, 0xEA, 0x3C, 0xD3, 0x03, 0x74, 0xE6, 0x46, 0xC0, 0x29, 0xAE, 0xB0,
		0x79, 0xBE, 0xCB, 0x18, 0x34, 0xBE, 0x5A, 0xE9, 0x19, 0x8F, 0xA3, 0x8F, 0xD6, 0x6A, 0x6C, 0x88,
		0x1E, 0x21, 0x08, 0x15, 0xC4, 0xE7, 0xE6, 0xBA, 0x97, 0x9C, 0x4F, 0x89, 0x9F, 0x1A, 0x67, 0x4F,
		0xC0, 0xD5, 0x72, 0x51, 0x16, 0xB4, 0xD3, 0x8A, 0x1F, 0xE3, 0x92, 0x02, 0x7F, 0x59, 0x56, 0x8F,
		0x07, 0x8D, 0xC1, 0xC2, 0x42, 0x69, 0x3C, 0xA6, 0xBF, 0x3D, 0xDF, 0x0D, 0xAA, 0x4F, 0x7E, 0x80,
		0x07, 0x11, 0xE2, 0x94, 0x19, 0x9B, 0x16, 0x26, 0x1A, 0x46, 0x09, 0x0D, 0xB5, 0xB8, 0x8E, 0x01,
		0x9C, 0xFE, 0x09, 0xB3, 0x60, 0xC2, 0xAE, 0x50, 0x3C, 0x68, 0x75, 0x4A, 0x57, 0xD8, 0x4F, 0xD7,
		0xA2, 0x76, 0x2C, 0xC1, 0xA2, 0x23, 0xBC, 0x54, 0x2A, 0xDD, 0xF3, 0xDD, 0xA7, 0x34, 0xF7, 0x5C,
		0xF4, 0x86, 0x23, 0x48, 0x7C, 0x3F, 0x05, 0x40, 0x0E, 0xB0, 0xE5, 0xEB, 0x3E, 0xDF, 0x6A, 0x83,
		0x65, 0xA0, 0xB2, 0x06, 0xD1, 0x40, 0x79, 0x0D, 0xDE, 0x95, 0x84, 0x96, 0x87, 0x6F, 0xCE, 0x48,
		0x24, 0x13, 0x0B, 0xF5, 0xC7, 0xF5, 0xA8, 0x7F, 0x2E, 0xC7, 0xE1, 0xBA, 0xAE, 0x2B, 0xF7, 0xF0,
		0x8E, 0xF7, 0x54, 0x0B, 0xF0, 0xD2, 0x41, 0x81, 0x68, 0x3B, 0x1E, 0x35, 0xAB, 0xD9, 0x2B, 0x46,
		0x57, 0xE8, 0x53, 0xDF, 0xDE, 0x10, 0xEF, 0xCB, 0x4C, 0xE0, 0x52, 0x18, 0x2C, 0x4E, 0xB9, 0x20,
		0xE9, 0x7E, 0x85, 0xDF, 0x75, 0x32, 0xE6, 0x10, 0xE9, 0x9C, 0x7B, 0x2E, 0x4C, 0xDA, 0x46, 0xE6,
		0xCC, 0x77, 0x36, 0x1D, 0x4A, 0x15, 0xF5, 0x32, 0x18, 0x6B, 0x7E, 0xAA, 0xCC, 0x97, 0xCC, 0xD1,
		0x2F, 0xE5, 0x58, 0x03, 0x35, 0x35, 0x3D, 0xA0, 0x2B, 0x13, 0x3A, 0x65, 0xFF, 0x24, 0x72, 0xCF,
		0xA7, 0x6D, 0x52, 0x55, 0xF6, 0xC2, 0x30, 0x23, 0x7D, 0x9B, 0x9E, 0xB0, 0x94, 0x02, 0xAD, 0x60,
		0x8A, 0x9F, 0xBC, 0xC8, 0xE4, 0x2B, 0x92, 0x96, 0xF5, 0xAE, 0x04, 0xA4, 0x33, 0x0C, 0x90, 0x67,
		0xF0, 0xB9, 0x1E, 0x7E, 0xBE, 0x02, 0x18, 0xB2, 0x03, 0xB6, 0x40, 0xBF, 0x05, 0xE3, 0x76, 0x98,
		0x21, 0x38, 0xC9, 0x5F, 0xD3, 0x51, 0x8B, 0x43, 0x0B, 0x1A, 0x0B, 0xF9, 0x3C, 0x21, 0x6C, 0x3D,
		0xB8, 0xA0, 0x57, 0xCA, 0x68, 0xCD, 0x1E, 0xD2, 0x2C, 0x50, 0xEE, 0xC0, 0xDF, 0x25, 0x88, 0x52,
		0x37, 0xE1, 0x44, 0xC6, 0x76, 0x3B, 0x91, 0x95, 0x86, 0x76, 0x87, 0x49, 0x21, 0x93, 0x44, 0x0A,
		0x52, 0xB9, 0x2D, 0x2B, 0xE3, 0x1D, 0xB0, 0xE4, 0x98, 0xC6, 0xEE, 0x3D, 0x96, 0x53, 0x4B, 0xFF,
		0x39, 0x00, 0xD5, 0x42, 0x7E, 0xE1, 0x4C, 0x6F, 0xD5, 0xB7, 0xE6, 0x99, 0x2A, 0x5B, 0x67, 0xEE,
		0x3E, 0xBA, 0xF7, 0xEC, 0x43, 0x2A, 0x1C, 0xB6, 0xB5, 0x04, 0x26, 0x59, 0xB1, 0x4C, 0x17, 0xCC,
		0x83, 0xB9, 0x00, 0x3E, 0x36, 0x91, 0x90, 0xF7, 0x5E, 0x38, 0xDC, 0xE4, 0x15, 0xC7, 0x67, 0xF0,
		0xCA, 0xC8, 0xD2, 0x91, 0x5D, 0x74, 0xAC, 0x97, 0x56, 0x36, 0x1A, 0x82, 0x0A, 0xAA, 0xB4, 0x4E,
		0xBF, 0x29, 0x5C, 0xBF, 0x58, 0xB3, 0x97, 0xF9, 0xEB, 0x7C, 0x85, 0xB4, 0xA5, 0x13, 0x2F, 0xD1,
		0xDE, 0x1C, 0xEC, 0x97, 0xDD, 0xE2, 0x39, 0xE4, 0xFB, 0x0A, 0x02, 0xE0, 0xC3, 0xBA, 0x39, 0x79,
		0xAA, 0x1C, 0x37, 0x75, 0x25, 0x54, 0xBE, 0x85, 0x74, 0x2C, 0xFA, 0x0C, 0xFA, 0x50, 0xF6, 0xBE,
		0x9F, 0x2A, 0x53, 0x7C, 0x27, 0x46, 0x68, 0x2D, 0x74, 0x2B, 0x46, 0xDA, 0xF5, 0x07, 0x95, 0x09,
		0x6A, 0x91, 0xB7, 0xB1, 0x34, 0x07, 0x5F, 0xEA, 0xBE, 0x0F, 0x87, 0x28, 0x68, 0x97, 0x43, 0x77,
		0xD5, 0x38, 0x2B, 0x11, 0x11, 0x4F, 0xD9, 0x75, 0x5E, 0xE1, 0x06, 0xA0, 0x3B, 0xAC, 0x32, 0xFE,
		0xBF, 0x73, 0x59, 0x5B, 0xA2, 0xA8, 0x7E, 0x10, 0x4C, 0x6E, 0x78, 0xF0, 0x4A, 0x4E, 0x95, 0xD6,
		0xDD, 0x05, 0x7A, 0xBB, 0xF1, 0xEB, 0xA8, 0xA4, 0x5D, 0x91, 0xF0, 0xED, 0xDB, 0xB8, 0x01, 0x41,
		0xF8, 0x97, 0x7F, 0xC3, 0x91, 0x53, 0xBF, 0xE9, 0xEA, 0x33, 0x1F, 0xDC, 0xA6, 0xE6, 0x8D, 0xCB,
		0x75, 0xD0, 0x69, 0xD0, 0xA4, 0x59, 0xA5, 0x02, 0xFC, 0x60, 0x0D, 0x6A, 0xA0, 0x05, 0x1A, 0x54,
		0x8A, 0xA7, 0x57, 0xA3, 0xF0, 0x90, 0x8A, 0xD5, 0x6F, 0x1E, 0x2E, 0x10, 0x9A, 0x93, 0x2B, 0x51,
		0x2C, 0xFD, 0x99, 0xE5, 0x9B, 0x5D, 0xB2, 0xA7, 0x37, 0x99, 0x26, 0x35, 0xCA, 0xDD, 0x22, 0x19,
		0x59, 0x2A, 0xB0, 0x99, 0x23, 0xDF, 0xA7, 0xA9, 0x85, 0x12, 0xCF, 0xBF, 0xFC, 0x74, 0x80, 0x87,
		0xE1, 0x97, 0xD0, 0xF9, 0xEF, 0x5F, 0x1B, 0x45, 0xF7, 0x76, 0xDB, 0x66, 0x39, 0x05, 0x43, 0x06,
		0xA9, 0x9F, 0x2E, 0x14, 0x9F, 0x1C, 0x0C, 0x1F, 0xD5, 0xD9, 0xA4, 0x8D, 0x18, 0x6F, 0x08, 0x53,
		0x0B, 0x92, 0x9A, 0x0C, 0xEA, 0x4C, 0xE4, 0x1D, 0x9E, 0x9A, 0x51, 0xB8, 0x7E, 0x2D, 0xE7, 0x3C,
		0xFF, 0x84, 0x5C, 0xBF, 0x8F, 0x8C, 0x89, 0x09, 0x1B, 0x7E, 0x4B, 0xE7, 0x85, 0xEC, 0x04, 0xB5,
		0x20, 0x18, 0x1E, 0x55, 0xD5, 0x5B, 0xAC, 0xC6, 0x25, 0x5A, 0xA1, 0x81, 0xC1, 0x31, 0x9C, 0xF5,
		0xB5, 0x54, 0x07, 0x65, 0x0A, 0x5B, 0x90, 0x06, 0x4F, 0x84, 0xB2, 0x7F, 0xD1, 0xAD, 0x16, 0x81,
		0x25, 0xAF, 0xAF, 0xE2, 0x03, 0xA9, 0x1F, 0x13, 0x02, 0x5D, 0x54, 0x89, 0xCD, 0x44, 0x51, 0xEB,
		0xA4, 0x2B, 0xBD, 0x47, 0xB0, 0xB6, 0x27, 0x1D, 0x9B, 0x14, 0x6F, 0xBF, 0xCD, 0x59, 0xBC, 0x0A,
		0x37, 0xA8, 0x74, 0x7D, 0x16, 0x90, 0x28, 0xD5, 0x94, 0xC3, 0xE4, 0x23, 0xC4, 0x98, 0x91, 0xCE,
		0x55, 0xBD, 0x21, 0x3B, 0x84, 0xBD, 0x44, 0x3C, 0xF9, 0xCD, 0x37, 0x43, 0x4A, 0xC6, 0x8C, 0x23,
		0x04, 0x28, 0x63, 0x7A, 0x03, 0x85, 0xD2, 0x46, 0x93, 0xCA, 0xFE, 0xC3, 0x83, 0x0B, 0x13, 0xCC,
		0x5D, 0xCB, 0xBA, 0xCA, 0x68, 0xAB, 0x05, 0xF7, 0xEC, 0x4A, 0x9C, 0x0F, 0xD5, 0xC4, 0x5A, 0xA5,
		0xA0, 0x04, 0x41, 0x6A, 0xF6, 0xEF, 0x16, 0x9B, 0x69, 0x38, 0xF6, 0x2D, 0xAA, 0xEB, 0x2D, 0xE2,
		0x82, 0xA2, 0x9F, 0x6F, 0xBD, 0x2A, 0xE3, 0x66, 0x6B, 0x21, 0xDA, 0x56, 0xAD, 0x82, 0x2B, 0x93,
		0xF3, 0x25, 0xEA, 0xFC, 0xFD, 0xFD, 0x1B, 0xA9, 0xFC, 0xB8, 0xC6, 0x98, 0x45, 0xF2, 0x70, 0x03,
		0x4A, 0x9C, 0x60, 0x82, 0x65, 0xB6, 0x68, 0x4C, 0xE7, 0x41, 0x10, 0x9D, 0x59, 0x40, 0x03, 0x02,
		0x07, 0x12, 0x33, 0xAF, 0x79, 0xE1, 0xC4, 0xEB, 0xB8, 0xCE, 0x6A, 0x90, 0x72, 0x61, 0x5D, 0x56,
		0xC7, 0x59, 0x31, 0xCB, 0x45, 0x2D, 0x42, 0x9F, 0x10, 0x1D, 0x09, 0x63, 0x59, 0x8C, 0x6C, 0xDB,
		0x11, 0xCF, 0xA1, 0xDF, 0x5F, 0x4D, 0xDF, 0xB4, 0xC3, 0x82, 0xEE, 0x58, 0x16, 0xB4, 0x74, 0xFA,
		0xBE, 0x11, 0x9C, 0x1E, 0x98, 0x29, 0xDE, 0xE3, 0xE5, 0x9E, 0xCF, 0xD7, 0x91, 0x0A, 0xA3, 0xA4,
		0x42, 0xA1, 0x95, 0x09, 0x9E, 0x16, 0xD5, 0xA8, 0x24, 0x56, 0x5B, 0x23, 0xC8, 0x56, 0x4C, 0xCB,
		0x89, 0x18, 0x69, 0xEB, 0x0C, 0x1F, 0xC0, 0x41, 0x5C, 0x63, 0x04, 0x68, 0xB2, 0x0F, 0x3F, 0x88,
		0x36, 0xDD, 0x23, 0x4D, 0x4C, 0xC0, 0x81, 0xE3, 0xE9, 0xAD, 0xE0, 0x27, 0xD5, 0xE5, 0x46, 0xEB,
		0xFF, 0x32, 0xA2, 0xB7, 0x14, 0x64, 0x0B, 0x6D, 0x1B, 0xE5, 0xD8, 0xAE, 0x9D, 0xE8, 0x55, 0xB9,
		0x52, 0x70, 0x59, 0xB8, 0x72, 0x92, 0x69, 0x37, 0x95, 0x61, 0x0A, 0xE5, 0xF6, 0x55, 0x97, 0x1D,
		0xBF, 0xF7, 0x29, 0x77, 0x0F, 0x72, 0x80, 0xB2, 0x7E, 0x56, 0xBF, 0xFD, 0xE9, 0xF5, 0x9B, 0x62,
		0xE9, 0xBD, 0x0B, 0xC2, 0x07, 0x55, 0x31, 0x4C, 0x57, 0x3A, 0x05, 0xB9, 0x27, 0x41, 0x4A, 0xC3,
		0xEC, 0x72, 0x20, 0xB3, 0x0C, 0xF9, 0xD9, 0x3A, 0x14, 0x6A, 0x03, 0x44, 0x6A, 0xF1, 0x41, 0x55,
		0x7F, 0x81, 0xC2, 0x04, 0xA8, 0x05, 0xB9, 0x49, 0x2E, 0x43, 0xC4, 0x00, 0x87, 0x86, 0x04, 0xAC,
		0xAF, 0x73, 0x78, 0x0E, 0xA4, 0x43, 0x5B, 0x36, 0xA2, 0x8F, 0x9C, 0xF7, 0x66, 0x4A, 0x5A, 0x09,
		0x6B, 0xAA, 0x69, 0x6F, 0xB1, 0x20, 0x0D, 0x56, 0x85, 0x0A, 0x5E, 0x06, 0xBF, 0xE2, 0x32, 0xB4,
		0x5C, 0x46, 0x33, 0x0D, 0x27, 0xA3, 0x6B, 0xE1, 0xB2, 0x6A, 0x7D, 0x4A, 0xA7, 0x81, 0x0F, 0x2B,
		0x16, 0x7C, 0x51, 0xD6, 0xC0, 0x3D, 0xB9, 0xFE, 0xB4, 0x66, 0xC4, 0xB6, 0x54, 0x53, 0x67, 0xDA,
		0x70, 0x96, 0x9A, 0x0A, 0x07, 0x1A, 0x26, 0xBA, 0x85, 0x50, 0xF5, 0x27, 0x53, 0x9C, 0x3A, 0x94,
		0x0A, 0x7D, 0xDB, 0xE1, 0xC3, 0xE3, 0x6A, 0x3E, 0x9E, 0xD5, 0x13, 0x0A, 0xA3, 0xD2, 0x21, 0x75,
		0x79, 0x17, 0x26, 0xAC, 0x48, 0x5F, 0x3D, 0xE1, 0x7D, 0xA4, 0xB1, 0x56, 0x0F, 0x92, 0x2C, 0x60,
		0xE6, 0xCB, 0x87, 0x35, 0xB8, 0x75, 0xC3, 0xA2, 0x03, 0x50, 0x4B, 0xA2, 0x6E, 0x01, 0xE1, 0xDD,
		0x87, 0xA5, 0x33, 0xC6, 0x2F, 0xA2, 0x41, 0xFC, 0x72, 0x98, 0xA2, 0x69, 0x4C, 0x3F, 0xF0, 0x53,
		0xF5, 0x41, 0x2B, 0x23, 0x24, 0x3B, 0xCE, 0x9D, 0x39, 0x31, 0x17, 0x08, 0xE1, 0x3F, 0x5F, 0xFB,
		0x00, 0xFA, 0xF1, 0xE3, 0xE1, 0x7B, 0x0C, 0xDF, 0x8D, 0xA2, 0xC4, 0xCD, 0x62, 0x3D, 0xAE, 0xC7,
		0x48, 0x09, 0x1C, 0x66, 0xCB, 0x0E, 0x23, 0xE8, 0x1B, 0x9F, 0x1B, 0xCB, 0xF8, 0x14, 0xC3, 0x34,
		0x91, 0x32, 0x2B, 0x39, 0x1C, 0xBA, 0x1C, 0xA0, 0x19, 0xF2, 0x57, 0x9D, 0x78, 0x00, 0x55, 0x1F,
		0x15, 0x12, 0x9A, 0xA2, 0xF2, 0xC2, 0xB7, 0x4E, 0xEA, 0x46, 0x01, 0xC2, 0xE9, 0x76, 0xBF, 0xDE,
		0xCF, 0x8B, 0xC7, 0x50, 0x80, 0xEE, 0x46, 0x91, 0x93, 0x1E, 0x5C, 0x48, 0x5D, 0xC8, 0xC8, 0x63,
		0xD1, 0x89, 0x02, 0x29, 0xE9, 0x90, 0x9F, 0x0B, 0x0A, 0x1A, 0x44, 0x17, 0xE7, 0x4E, 0xAD, 0x58,
		0x55, 0xF8, 0x38, 0xF6, 0x4F, 0xD8, 0x1C, 0x7E, 0x25, 0x9B, 0x59, 0x16, 0xBC, 0x65, 0x24, 0xC5,
		0xA7, 0x56, 0xE5, 0x20, 0x3F, 0xD9, 0x27, 0xE0, 0x32, 0x24, 0xE1, 0x7B, 0xE1, 0x32, 0xEA, 0xF4,
		0xFE, 0xD9, 0xA5, 0xFF, 0x35, 0xAE, 0xA9, 0x1B, 0x38, 0x28, 0x6A, 0xC0, 0x1A, 0x42, 0xD9, 0x5E,
		0x14, 0x2C, 0xC2, 0x2D, 0x9B, 0x94, 0x5B, 0xCF, 0x83, 0x30, 0xB9, 0x06, 0xAF, 0x4B, 0xD7, 0xF6,
		0x38, 0x7C, 0xFF, 0xB4, 0xA5, 0x1A, 0xA0, 0xE9, 0xF3, 0x01, 0xE3, 0x97, 0xC4, 0xA9, 0x57, 0xF5,
		0xB9, 0x96, 0xA7, 0xA3, 0xB8, 0x10, 0x0E, 0xFB, 0x1D, 0x39, 0x44, 0x16, 0x97, 0x94, 0x3E, 0x5F,
		0xAF, 0x0F, 0xE3, 0x99, 0xDC, 0xA0, 0xE9, 0x8D, 0x26, 0x2B, 0xD9, 0xAE, 0xEC, 0x4C, 0x4F, 0x09,
		0x86, 0x7E, 0x7B, 0xC3, 0xE3, 0xC6, 0x17, 0xAE, 0x30, 0x9C, 0x31, 0xD1, 0x84, 0x47, 0xAF, 0xCB,
		0xEA, 0x69, 0x2A, 0x08, 0x3E, 0x13, 0x00, 0xDE, 0xF6, 0x4A, 0x42, 0xD3, 0xBE, 0x33, 0xD9, 0x50,
		0x6B, 0x8D, 0x59, 0x12, 0x1A, 0xD3, 0xA7, 0x7C, 0x0A, 0xE7, 0x87, 0x47, 0xCA, 0xAA, 0x33, 0xFD,
		0xC1, 0xF6, 0x28, 0xC1, 0x62, 0xA2, 0x4C, 0x79, 0x83, 0x48, 0x86, 0x0E, 0xA4, 0x67, 0x34, 0x95,
		0xAE, 0x7D, 0xD6, 0xEE, 0x91, 0x05, 0x35, 0x91, 0xE8, 0x34, 0x39, 0xA3, 0xE5, 0xE6, 0x80, 0x53,
		0x76, 0x1F, 0x94, 0xA0, 0xF6, 0xA5, 0x41, 0x79, 0x82, 0xD3, 0xB0, 0x1F, 0xCE, 0xE1, 0x86, 0x64,
		0x65, 0x0C, 0x8D, 0xD6, 0xFA, 0xC1, 0x10, 0x6C, 0x07, 0xD5, 0xF0, 0x77, 0x65, 0xB9, 0x0C, 0xBD,
		0xAE, 0x2D, 0x62, 0x6C, 0x42, 0x7E, 0x2A, 0xBE, 0x5F, 0xC1, 0x17, 0x3B, 0x07, 0xFF, 0x5E, 0xD7,
		0x31, 0x52, 0x26, 0x2F, 0x9F, 0x12, 0xD8, 0x2E, 0xA3, 0xF5, 0xB5, 0xD2, 0xFC, 0x6E, 0x08, 0x1F,
		0xC8, 0x93, 0xA1, 0xEB, 0xF9, 0x13, 0x1D, 0x1F, 0x98, 0x5E, 0xB0, 0x0C, 0x65, 0x6C, 0xAE, 0x07,
		0x78, 0xF8, 0x12, 0xD2, 0xD1, 0x1E, 0x77, 0x5C, 0x24, 0x62, 0xE5, 0x94, 0xD6, 0x6A, 0x8E, 0xD0,
		0x72, 0x59, 0xDA, 0x48, 0x38, 0x2F, 0x31, 0x75, 0x0C, 0x52, 0xF0, 0x0C, 0x8F, 0x5C, 0xE9, 0x5E,
		0x5A, 0x94, 0xE8, 0xD2, 0x80, 0xF8, 0x4F, 0xE7, 0xAA, 0x6C, 0xBE, 0x47, 0xFB, 0xDD, 0x57, 0x0A,
		0xD8, 0x5E, 0xCC, 0x0D, 0x8F, 0x42, 0x5E, 0xDC, 0x5D, 0x95, 0x95, 0x60, 0x9B, 0x6F, 0x05, 0x5E,
		0x08, 0x45, 0x91, 0xE4, 0xB8, 0x06, 0xB1, 0xF2, 0xC0, 0xD7, 0xE3, 0x47, 0xB7, 0x38, 0x08, 0xA8,
		0x58, 0xE4, 0x55, 0xFC, 0xE2, 0x37, 0x1F, 0x38, 0xA2, 0x18, 0x9E, 0xC2, 0x0F, 0x90, 0x14, 0x20,
		0x50, 0xD1, 0xD0, 0xAB, 0x36, 0x7F, 0xAA, 0x03, 0x1C, 0xE6, 0x0A, 0xF9, 0x8E, 0x41, 0xDB, 0x32,
		0x1C, 0x68, 0xA0, 0xA0, 0xED, 0x4A, 0xF4, 0x4B, 0x09, 0xD0, 0xF0, 0x01, 0x8B, 0x17, 0x44, 0xE1,
		0xEA, 0xC5, 0x9D, 0x3B, 0x37, 0x7A, 0x68, 0xF1, 0x78, 0x46, 0xCF, 0xB6, 0x57, 0xDB, 0x4B, 0x5C,
		0x03, 0xE1, 0x9D, 0xC0, 0x37, 0x55, 0x8D, 0x03, 0xFB, 0x6A, 0x00, 0x82, 0x19, 0xD1, 0xC0, 0x76,
		0x97, 0xEE, 0xC9, 0xAD, 0x0D, 0x72, 0x0B, 0xE9, 0xA8, 0x09, 0x92, 0x03, 0xA4, 0xAA, 0x2C, 0xCF,
		0xFD, 0xDE, 0x86, 0xD0, 0x06, 0x4A, 0xAE, 0x7E, 0xC1, 0xB8, 0x2A, 0x4E, 0x9F, 0xA3, 0x5E, 0x8C,
		0x12, 0x40, 0x74, 0x38, 0xE7, 0xEA, 0xB0, 0x51, 0xC2, 0xB9, 0x6D, 0x4A, 0x50, 0xBF, 0x59, 0x9C,
		0x05, 0xB2, 0x42, 0xE2, 0x0F, 0x71, 0x44, 0xDB, 0x97, 0x0B, 0xD0, 0xDB, 0x44, 0x1F, 0x9A, 0x3B,
		0x18, 0x2A, 0x7B, 0xD9, 0x03, 0x83, 0x0B, 0xCF, 0x27, 0x20, 0x43, 0xA6, 0x42, 0xED, 0x89, 0x63,
		0xDB, 0x2D, 0x27, 0xC2, 0x3B, 0xE6, 0x0D, 0x3E, 0xB6, 0x96, 0x33, 0x70, 0xA6, 0xF3, 0xF5, 0x56,
		0xEA, 0xEB, 0xF1, 0xE7, 0xD8, 0xCB, 0x04, 0x48, 0x99, 0x4C, 0x00, 0xA4, 0x2A, 0xA5, 0x8A, 0xF1,
		0x58, 0xD5, 0x17, 0x4C, 0xC5, 0x88, 0x06, 0x8F, 0xA6, 0x67, 0xA6, 0x14, 0xC7, 0xB9, 0xE0, 0x86,
		0xAC, 0x67, 0xFD, 0xB3, 0x5B, 0x3E, 0xDF, 0x03, 0xFD, 0xC8, 0xC4, 0x4A, 0x32, 0x78, 0x6B, 0xD1,
		0xC1, 0xE2, 0x36, 0x9D, 0x0B, 0xF2, 0x54, 0x25, 0xB8, 0xB7, 0xB2, 0x10, 0x7A, 0xA6, 0x79, 0x52,
		0xC2, 0xEE, 0x98, 0xA5, 0x3D, 0xF0, 0x07, 0x8D, 0x25, 0xC3, 0xAC, 0xFD, 0xCF, 0x83, 0x98, 0x80,
		0x56, 0x95, 0xC4, 0x14, 0xA2, 0xA5, 0x93, 0xFE, 0x24, 0x59, 0x44, 0x73, 0xDF, 0xD6, 0x47, 0xDA,
		0x22, 0x3A, 0x82, 0xC5, 0xD1, 0x59, 0x40, 0x9D, 0x0C, 0x1A, 0xB7, 0x79, 0x45, 0x9A, 0xF8, 0x6D,
		0x5A, 0x5C, 0xC2, 0x80, 0xFC, 0xAA, 0x8A, 0xA4, 0xFE, 0x68, 0x61, 0x7D, 0xFE, 0x2C, 0x36, 0xE3,
		0xE0, 0x59, 0x28, 0x40, 0x79, 0xAD, 0x2D, 0x28, 0x12, 0x30, 0xFC, 0x56, 0x2E, 0x1D, 0xEC, 0x48,
		0x3A, 0xF0, 0xC5, 0x6C, 0x31, 0xE0, 0x2E, 0xB3, 0x91, 0x70, 0xB9, 0x9E, 0xBD, 0xE7, 0x96, 0x58,
		0xCB, 0xBC, 0x1C, 0xE4, 0xC7, 0x78, 0xC7, 0x1E, 0x39, 0xDB, 0xB8, 0x77, 0x50, 0xB7, 0x65, 0x20,
		0x04, 0x16, 0x8B, 0xFC, 0x66, 0xC4, 0x6D, 0x05, 0x8C, 0x3C, 0xB6, 0x32, 0x2F, 0xDE, 0xC3, 0x6F,
		0xFC, 0x82, 0x06, 0x02, 0x87, 0x47, 0xFD, 0xD8, 0xDA, 0x75, 0xE0, 0x4E, 0x8C, 0x40, 0x00, 0xB2,
		0x9B, 0x35, 0x78, 0xA4, 0x61, 0x64, 0x96, 0x62, 0x37, 0xF6, 0x3E, 0x39, 0xFA, 0x14, 0x5B, 0xC4,
		0x70, 0x17, 0xDC, 0x0C, 0x9E, 0x31, 0x82, 0x2C, 0x63, 0xCC, 0x8A, 0x43, 0x7C, 0x69, 0x12, 0x05,
		0x18, 0xA3, 0x62, 0xCC, 0xA2, 0x13, 0x96, 0x25, 0xA6, 0x1B, 0xF2, 0x10, 0xC8, 0x73, 0x4F, 0xCB,
		0x80, 0xCA, 0xAF, 0x73, 0xC9, 0x78, 0xB1, 0xAE, 0x87, 0xB8, 0xDF, 0x50, 0xD3, 0x55, 0x1E, 0x3A,
		0x81, 0xF6, 0x84, 0xD6, 0x57, 0x36, 0xCF, 0x38, 0xB7, 0xBC, 0xBC, 0x1E, 0x48, 0x62, 0x9F, 0x0F,
		0x0C, 0xE5, 0xF0, 0x63, 0x33, 0xE6, 0x59, 0x6B, 0x1E, 0xE6, 0x1C, 0x8A, 0xF9, 0xDD, 0x6B, 0xA3,
		0xDC, 0x02, 0x4A, 0x2F, 0x8C, 0x6A, 0x8D, 0x16, 0x7E, 0x2F, 0xF1, 0x75, 0xD5, 0x15, 0x93, 0x07,
		0x27, 0xD9, 0x6F, 0x1A, 0x5D, 0x43, 0xF3, 0x47, 0xC4, 0xED, 0xAD, 0x05, 0x9F, 0xEC, 0x8F, 0xD0,
		0xBE, 0xB5, 0x58, 0xF4, 0xF6, 0xBE, 0x08, 0x73, 0x96, 0x19, 0x05, 0x25, 0xEC, 0x3D, 0x26, 0xF4,
		0x93, 0xDB, 0x9F, 0x56, 0x48, 0x4C, 0xBC, 0xD0, 0x02, 0x59, 0xD1, 0x40, 0x4C, 0xA6, 0x06, 0x41,
		0xE8, 0x7D, 0x47, 0xAE, 0x3A, 0x9E, 0x1A, 0x71, 0x52, 0xD4, 0x67, 0xC1, 0x14, 0x7E, 0x40, 0x6F,
		0x1C, 0x75, 0x30, 0x7B, 0x70, 0x3A, 0xE0, 0x37, 0xB7, 0x41, 0x7F, 0xCB, 0x4A, 0xBA, 0xA7, 0xCE,
		0x56, 0x54, 0xC5, 0x46, 0x65, 0x6F, 0xB4, 0xB6, 0xF0, 0x57, 0xCE, 0x2E, 0x4F, 0xA9, 0xF0, 0x14,
		0x50, 0xC3, 0x30, 0xC5, 0xBA, 0xE1, 0x5E, 0xD6, 0xDC, 0xC5, 0x78, 0x55, 0x32, 0xAA, 0xCB, 0x29,
		0x35, 0x81, 0x46, 0x5E, 0x92, 0xE7, 0xDE, 0xCC, 0x92, 0x29, 0x86, 0xE0, 0x8F, 0x91, 0x3C, 0x74,
		0x97, 0x79, 0x63, 0x97, 0x4A, 0xCC, 0x88, 0xB5, 0xA3, 0x7A, 0xF0, 0xF0, 0x33, 0x87, 0xCD, 0xBD
	};
	uint8_t b = Value ^ hdSurEncBasicCrypt_D2_0F_11(Value, lookup[(((AddrInd * 2) + 0) * 256) + Value]);
	return (Value ^ hdSurEncBasicCrypt_D2_0F_11(b, lookup[(((AddrInd * 2) + 1) * 256) + b]));
}

void hdSurEncPhase1_D2_0F_11(uint8_t *CWs)
{
	static const uint8_t lookup1[] =
	{
		0x16, 0x71, 0xCA, 0x14, 0xC4, 0xF4, 0xA3, 0x5A, 0x9D, 0x5F, 0x85, 0x8B, 0xA6, 0x77, 0xFD, 0x3C,
		0x5F, 0x13, 0x2A, 0x5F, 0x61, 0x36, 0xE4, 0xDC, 0x0D, 0x82, 0x92, 0xC5, 0x25, 0xE1, 0x7A, 0x1C,
		0x29, 0x19, 0x94, 0x2F, 0xC5, 0xD2, 0xDC, 0xBA, 0x86, 0x60, 0x64, 0x60, 0x86, 0x92, 0xA3, 0x4E,
		0x3D, 0x9B, 0xCC, 0x16, 0xBB, 0xBA, 0xD2, 0xF0, 0x6A, 0xD3, 0x2F, 0x07, 0x75, 0xBD, 0x28, 0xDB
	};
	static const int8_t lookup2[] = { 1, -1, -1, 1, -1, 2, 1, -2, -1, 1, 2, -2, 1, -2, -2, 4 };
	static const int8_t CAddrIndex[] = { 0, 1, 3, 4 };
	int32_t i, j, i1, i2, i3;

	for(i = 3; i >= 0; --i)
	{
		for(j = 0; j <= 15; ++j)
		{
			CWs[j] = CWs[j] ^ hdSurEncBasicCrypt_D2_0F_11(j , lookup1 [(16 * i) + j]);
		}

		uint8_t Buffer[16];
		uint32_t k;
		for(i1 = 0; i1 <= 3; ++i1)
		{
			for(i2 = 0; i2 <= 3; ++i2)
			{
				k = 0;
				for(i3 = 0; i3 <= 3; ++i3)
				{
					k = k + (CWs[(i2 * 4) + i3] * lookup2[(i3 * 4) + i1]);
					Buffer[(i2 * 4) + i1] = (uint8_t)k;
				}
			}
		}
		memcpy(CWs, Buffer, 16);

		// CW positions are mixed around here
		uint8_t a4[4];
		for(i1 = 1; i1 <= 3; ++i1)
		{
			for(i2 = 0; i2 <= 3; ++i2)
			{
				a4[i2] = i1 + (i2 * 4);
			}

			for(i2 = 0; i2 <= i1 - 1; ++i2) // the given code in Func1_3 seems to be wrong here(3 instead of i1-1)!
			{
				uint8_t tmp = CWs[a4[0]];

				for(i3 = 1; i3 <= 3; ++i3)
				{
					CWs[a4[i3 - 1]] = CWs[a4[i3]];
				}
				CWs[a4[3]] = tmp;
			}
		}

		for(i1 = 0; i1 <= 15; ++i1)
		{
			CWs[i1] = hdSurEncCryptLookup_D2_0F_11(CWs[i1], CAddrIndex[i1 & 3]);
		}
	}
}

void hdSurEncPhase2_D2_0F_11_sub(uint8_t *CWa, uint8_t *CWb, uint8_t AddrInd)
{
	uint8_t Buffer[8];
	uint8_t tmp, i;

	for(i = 0; i <= 7; ++i)
	{
		Buffer[i] = hdSurEncCryptLookup_D2_0F_11(CWb[i], AddrInd);
	}

	// some bitshifting
	tmp = Buffer[7];
	for(i = 7; i >= 1; --i)
	{
		Buffer[i] = ((Buffer[1] >> 4) & 0xFF) | ((Buffer[i - 1] << 4) & 0xFF);
	}
	Buffer[0] = ((Buffer[0] >> 4) & 0xFF) | ((tmp << 4) & 0xFF);

	// saving the result
	for(i = 0; i <= 7; ++i)
	{
		CWa[i] = CWa[i] ^ Buffer[i];
	}
}

void hdSurEncPhase2_D2_0F_11(uint8_t *CWs)
{
	hdSurEncPhase2_D2_0F_11_sub(CWs, CWs + 8, 0);
	hdSurEncPhase2_D2_0F_11_sub(CWs + 8, CWs, 1);
	hdSurEncPhase2_D2_0F_11_sub(CWs, CWs + 8, 2);
	hdSurEncPhase2_D2_0F_11_sub(CWs + 8, CWs, 3);
	hdSurEncPhase2_D2_0F_11_sub(CWs, CWs + 8, 4);
}

void CommonMain_1_D2_13_15(const uint8_t *datain, uint8_t *dataout)
{
	const uint8_t Tab3[88] = {
	0x1B, 0x12, 0x12, 0x0C, 0x12, 0x0C, 0x0C, 0x08, 0x09, 0x09, 0x06, 0x06, 0x06, 0x06, 0x04, 0x04,
	0x08, 0x04, 0x04, 0x02, 0x04, 0x02, 0x02, 0x01, 0x09, 0x06, 0x09, 0x06, 0x06, 0x04, 0x06, 0x04,
	0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x04, 0x04, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01,
	0x09, 0x06, 0x06, 0x04, 0x09, 0x06, 0x06, 0x04, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x02, 0x02,
	0x04, 0x02, 0x04, 0x02, 0x02, 0x01, 0x02, 0x01, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 };

	int i1,i2;
	unsigned long bb;

	for (i1 = 0; i1 < 11; i1++)
	{
		bb = 0;
		for (i2 = 0; i2 < 8; i2++)
		{
			bb += (Tab3[(i1 * 8) + i2] * datain[i2]);
		}
		dataout[i1] = (bb & 0xFF);
	}
}


unsigned short CommonMain_2_D2_13_15(const uint8_t *data, const unsigned long num)
{
	unsigned long bb1, bb2;

	bb1 = num >> 3;
	bb2 = (data[bb1] << 24) + (data[bb1 + 1] << 16) + (data[bb1 + 2] << 8);

	return ((bb2 >> (21 - (num & 7))) & 0x7FF);
}

void CommonMain_3_D2_13_15(uint8_t *data0, uint8_t *data1, int nbrloop)
{
	int i;
	unsigned long bb1, bb2;

	bb1 = 0;
	bb2 = 0;
	for (i = nbrloop - 1; i >= 0; i--)
	{
		bb1 += (data0[i] * 2) + data1[i];
		bb2 += data0[i] + data1[i];
		data0[i] = (bb1 & 0xFF);
		data1[i] = (bb2 & 0xFF);
		bb1 >>= 8;
		bb2 >>= 8;
	}
}

void CommonMain_D2_13_15(const uint8_t *datain, uint8_t *dataout, int loopval)
{

	const uint8_t Tab0_Comp[0x800] = {
	0x54,0x75,0x01,0x0C,0x7C,0xE2,0xC3,0xC2,0x5E,0x13,0x26,0xCA,0xB2,0xCD,0xB8,0x3D,
	0x02,0x2C,0xE4,0x19,0x41,0x3D,0xE4,0x0F,0xEC,0xF1,0x45,0x83,0xE2,0xE2,0x72,0xF9,
	0xCD,0x75,0x1E,0x41,0xCC,0x0C,0x1F,0x39,0x87,0x9B,0x46,0xFF,0x68,0x1F,0x00,0xD8,
	0x41,0x82,0xCA,0xC6,0xEF,0x87,0x90,0xA2,0x7E,0xD9,0xDE,0xC8,0x25,0xEA,0xC9,0x75,
	0x6E,0x18,0x81,0xD8,0x5A,0xA6,0x74,0x05,0xAF,0xAE,0xE0,0x4F,0x85,0xAD,0x94,0xF6,
	0x45,0xF4,0xF5,0x55,0xA8,0xEB,0xEC,0xDB,0x6C,0xFF,0x2F,0xC2,0xC3,0x7D,0x93,0xE6,
	0xF5,0x31,0x96,0xB7,0x9A,0xDB,0xE5,0x76,0x66,0xFB,0xDD,0xBC,0x19,0x18,0x42,0xC6,
	0x36,0xCD,0x46,0x33,0xEA,0xF1,0x4C,0xC0,0x72,0x07,0xCD,0x61,0xCE,0x0E,0x08,0x01,
	0xA3,0xFA,0x84,0x21,0xF2,0x43,0x37,0x1C,0xDE,0x25,0x8A,0x1A,0xF4,0xBB,0x40,0xF3,
	0x53,0xFE,0x17,0x60,0x91,0x6D,0x7B,0x6D,0x5F,0x1C,0x15,0x73,0xCC,0x6E,0x73,0x46,
	0x27,0x73,0xA3,0x10,0x16,0x32,0xB3,0x39,0x45,0xA6,0x55,0xE7,0x91,0x32,0x24,0xC8,
	0xAE,0xAF,0x1B,0x28,0x69,0x22,0x2F,0xE9,0x77,0x72,0xBF,0x4B,0x8B,0x07,0x82,0x31,
	0xB0,0x95,0x10,0x78,0x9F,0xC5,0xF3,0x73,0xE1,0xF8,0x36,0x84,0xFE,0x1B,0x92,0xB2,
	0xE6,0xA5,0xCE,0xDA,0x56,0x48,0x52,0x77,0x9D,0x9D,0x8E,0x37,0x4B,0xC8,0x35,0x7E,
	0xB9,0x5D,0xA4,0xAE,0x3F,0xD0,0xAA,0x60,0xA8,0x4C,0x85,0x49,0xF6,0x0C,0x27,0xE8,
	0x94,0x84,0xA0,0xAA,0x06,0x4D,0xAC,0x58,0x8B,0x61,0x29,0x3D,0x68,0x25,0xD3,0xD3,
	0x8E,0xA1,0xE0,0x71,0xBF,0x21,0x0C,0xC7,0x18,0x19,0xF1,0x25,0x98,0x5F,0x79,0x5E,
	0x51,0xBA,0x8C,0x2F,0x52,0x43,0xF3,0x5A,0xE3,0x58,0x97,0x64,0x23,0xE0,0x44,0x4F,
	0x30,0x2A,0xE0,0x16,0x8D,0x4D,0xD1,0x1F,0x7B,0xC9,0xC5,0x74,0x11,0x23,0x5D,0x95,
	0xAC,0x7F,0x2E,0x30,0xBE,0x2D,0xE3,0xB5,0xC6,0xA7,0x69,0x99,0x1F,0x18,0x3C,0x96,
	0x30,0x45,0x99,0x71,0x28,0x08,0x3C,0xF7,0x37,0x4F,0x6A,0xD6,0xAE,0x9B,0x57,0xC1,
	0xCC,0x2C,0xE2,0x0F,0x7D,0x66,0xF4,0x36,0x0C,0x3B,0x35,0xF6,0x28,0x03,0xA3,0x7A,
	0x83,0x15,0xF5,0x61,0x5E,0xE8,0xB7,0xD8,0x54,0x33,0x93,0x63,0x80,0x40,0x43,0xD0,
	0x9C,0xAA,0x3A,0x98,0x50,0xD2,0xB8,0x80,0x5D,0x16,0xDF,0x1C,0x03,0xAA,0x87,0xC7,
	0x63,0xA5,0x8D,0xA9,0x2E,0xFB,0x4F,0x7C,0x2B,0xF5,0xF9,0x57,0xB5,0x90,0xD8,0x75,
	0xAB,0x81,0x4C,0x1B,0xAF,0x6C,0x0E,0xCB,0xB1,0x4F,0xD3,0xE3,0x69,0x18,0x8C,0x7A,
	0x3C,0xE1,0x11,0x86,0x47,0x78,0x11,0xA0,0xD4,0x28,0xC3,0x0D,0xAC,0xC6,0x17,0xA1,
	0x32,0x9F,0x8F,0x42,0xD9,0x3F,0x66,0xD7,0x2D,0x87,0x7B,0x65,0xD3,0xD6,0x90,0x83,
	0xA2,0x75,0xE8,0x98,0x90,0x9D,0xDE,0x81,0x9E,0x3D,0xE4,0xA9,0xE4,0x0B,0xBC,0xBA,
	0x96,0xDD,0x05,0xCA,0xAE,0x78,0x69,0x24,0xDB,0xA7,0x3E,0x7A,0x3B,0xB4,0xC4,0x59,
	0x61,0xD2,0xF2,0xE3,0x99,0x8F,0x8F,0x8A,0x82,0x33,0xB8,0x17,0x5E,0x7A,0x32,0x41,
	0x10,0x8D,0xC2,0xEF,0xAA,0xF8,0x5A,0xF7,0xD2,0x1D,0xC0,0xCB,0x5E,0xB7,0x8A,0x78,
	0x49,0x42,0xEB,0x19,0x1B,0x61,0xA0,0x77,0x5A,0xF4,0x6D,0x55,0xDA,0xEB,0xCE,0x4E,
	0xB8,0xE6,0x32,0xD7,0x51,0x3F,0x73,0x14,0x34,0x6E,0x38,0xD6,0xA7,0x28,0x87,0x4A,
	0x59,0xCA,0x1C,0x80,0xB5,0x8C,0x9D,0x94,0xCB,0xFE,0x29,0x41,0xE5,0x69,0xCF,0xFD,
	0x0B,0xE1,0x7C,0xA1,0x70,0x12,0x76,0x43,0xDA,0xB9,0xD4,0xC3,0x31,0xBC,0x94,0x77,
	0x04,0xB4,0x1C,0xAA,0xEC,0x6F,0xA5,0x12,0x9D,0x6F,0x34,0x65,0x77,0xA0,0xD2,0x6F,
	0x60,0xC6,0x47,0xC2,0xDF,0x6A,0x10,0x53,0xD4,0xBA,0xF3,0xB7,0x38,0x79,0x63,0xC9,
	0xD4,0x77,0xBC,0x54,0xE9,0x79,0x42,0xD5,0xE0,0x71,0xE7,0x9E,0x5A,0x62,0x0C,0xAD,
	0x01,0x09,0xA8,0x9F,0x8E,0x67,0x4A,0x30,0xA4,0xB1,0x08,0xFC,0x0A,0xEA,0x7A,0x1D,
	0x4C,0x4A,0x21,0xDE,0x00,0xD7,0x41,0x98,0x6B,0x38,0x50,0x3E,0x1F,0x25,0x06,0xE3,
	0x6C,0xA3,0x84,0x5B,0xC1,0xED,0x47,0xDD,0xB3,0x83,0x46,0x72,0x69,0xCE,0x72,0x04,
	0x43,0x67,0x3A,0x19,0xD9,0x0A,0xF7,0x43,0x88,0xCA,0xC7,0x31,0x34,0x21,0x4E,0x4C,
	0xE8,0xD1,0x70,0x00,0xBD,0xB1,0xB6,0x76,0x6F,0x5B,0xF9,0xF5,0xF4,0x19,0x20,0x21,
	0xC1,0xF0,0x11,0x36,0x66,0xAB,0x15,0xBD,0x69,0x92,0xC6,0x46,0xDE,0xDC,0xE9,0x9A,
	0xF8,0x6C,0x15,0x29,0x15,0xA6,0x35,0x3E,0x08,0xE5,0x90,0x62,0x9F,0x86,0x56,0x83,
	0x5D,0x60,0x0D,0x22,0x77,0xA7,0x60,0x9B,0x26,0x80,0x16,0x67,0xB4,0x46,0xBF,0x74,
	0x55,0x92,0x5B,0x34,0xFF,0xC8,0x28,0x37,0xFF,0x14,0x62,0xFA,0xBD,0x03,0x78,0x04,
	0x1B,0x65,0x7F,0x99,0x05,0x27,0x14,0xC0,0x06,0x4D,0x4B,0x0E,0x98,0x34,0x6A,0xB3,
	0xA1,0xFE,0xBC,0x45,0x7D,0x52,0x50,0x0E,0x2C,0xFB,0x91,0xF5,0xFB,0x2A,0xB7,0xD9,
	0xB8,0xB8,0x54,0x31,0x81,0x03,0x93,0x2C,0xE1,0x5C,0xB9,0x2C,0xE8,0x38,0xC0,0xA7,
	0x58,0x18,0x92,0xC5,0x8B,0xEF,0x1E,0x33,0xA4,0xBA,0x86,0x2B,0xE9,0xEE,0xB1,0xDF,
	0xAB,0xB8,0x48,0xDA,0x84,0xF1,0x68,0x05,0x4E,0xDE,0xB5,0x9E,0x88,0x12,0xC9,0x60,
	0x50,0x58,0x56,0x9D,0x26,0x84,0xB6,0x1A,0xE6,0x4B,0x40,0x94,0x6D,0xE9,0x1D,0x0D,
	0x8A,0xF9,0x2A,0xB5,0xBC,0xDB,0x06,0x8F,0x13,0x7E,0x1D,0x1C,0xC7,0xFD,0x8F,0x78,
	0x55,0x3F,0x16,0x84,0x48,0xDA,0x1A,0xD1,0x93,0x95,0x20,0x58,0x92,0x39,0xF6,0x73,
	0x4E,0x9E,0x7B,0x70,0xFC,0x1E,0x5B,0x20,0x48,0x96,0xB3,0x7C,0x50,0x09,0x5B,0x61,
	0x57,0x97,0x36,0x04,0x29,0x2C,0x32,0x8E,0x93,0x4A,0x45,0xFA,0xD5,0x24,0x14,0x1A,
	0x28,0x9C,0x1A,0x71,0xAE,0x85,0x4B,0x26,0x79,0x99,0x65,0xD0,0x07,0x98,0xED,0xC9,
	0x1B,0x39,0x57,0x5B,0xDB,0x3D,0x87,0x69,0x66,0x9B,0x03,0x23,0x54,0x6B,0x4B,0xAC,
	0x6E,0x7A,0x25,0x1E,0xB6,0x97,0xCF,0x1D,0x07,0xCB,0x2A,0x3E,0x85,0x02,0x93,0x31,
	0x12,0x27,0xF0,0xA6,0x6D,0x0F,0x9A,0xB6,0xFC,0x22,0x79,0x6C,0x77,0xFD,0x3F,0xDC,
	0x19,0xD0,0xDF,0xBD,0x9E,0xE0,0xBE,0x20,0x13,0xA3,0x0A,0x0B,0x22,0xF2,0xC8,0x6B,
	0xA1,0xDD,0x6C,0x67,0xB3,0xFD,0x71,0xC2,0x7B,0x08,0x3B,0xF1,0x37,0xB5,0x0F,0x86,
	0xFA,0xA9,0xE9,0x42,0xD1,0xE8,0xCD,0x05,0xEF,0xD3,0xCC,0x0B,0x70,0x51,0x5B,0x97,
	0x06,0xC4,0x9D,0x88,0x11,0x3E,0x99,0x9F,0xBE,0x76,0x8C,0x8D,0xE6,0xBA,0xDA,0x48,
	0xD0,0x04,0x86,0x4F,0xA9,0xC6,0xB0,0xED,0xA4,0x94,0x46,0x96,0x27,0xEE,0x9F,0xBD,
	0xDA,0x9B,0x3D,0x11,0x80,0xD3,0x7B,0x5A,0x48,0x94,0xE5,0xCC,0x48,0xEA,0xE4,0x18,
	0xDF,0x51,0xB3,0x02,0x57,0x20,0x4B,0x0F,0x07,0xFF,0x41,0x33,0x0F,0x6B,0x2E,0xAA,
	0xDE,0xB2,0x56,0xF7,0xFB,0xA2,0x48,0x3C,0x97,0x1A,0x64,0x2C,0xD1,0x74,0x40,0xCF,
	0x65,0x7F,0x14,0x08,0x59,0xC4,0x35,0xD3,0x8A,0x0F,0xFD,0x71,0x7A,0x71,0xAC,0x2D,
	0xF3,0xFD,0x7B,0x12,0x5F,0xC0,0xBC,0x4E,0x96,0x12,0xF2,0x8E,0x41,0x84,0x01,0x0F,
	0xED,0x7B,0xC1,0xB9,0x39,0x03,0x35,0x40,0x49,0x53,0xB8,0xB4,0x6B,0xA6,0xE7,0x0A,
	0x14,0xBB,0x29,0x16,0xEC,0x2A,0x3A,0xD6,0x09,0xBB,0x5C,0x20,0xF8,0x09,0xFD,0x86,
	0xC4,0x25,0x09,0x85,0x0B,0xD5,0xD8,0x51,0xB1,0xA2,0xCB,0xDC,0xC4,0xDD,0x34,0xDF,
	0xE2,0x85,0xA9,0xCC,0x4E,0x66,0x51,0xFA,0x9C,0x4D,0xB7,0x1E,0x3E,0x49,0x34,0x9C,
	0x21,0x66,0x07,0x44,0xB2,0xEC,0x73,0xC5,0xBB,0x27,0x9A,0xA5,0x91,0x5A,0xB9,0x9F,
	0xBE,0xC8,0xA2,0x27,0x89,0x21,0xA7,0xEE,0x50,0x4D,0x43,0x50,0x67,0xC2,0x3B,0x7C,
	0x20,0x0B,0x95,0x40,0xBE,0xEA,0xB5,0xD9,0x82,0xD7,0x9C,0xB5,0x21,0xAD,0xA6,0xF9,
	0x70,0xEA,0xCD,0x04,0xDD,0x58,0x91,0x89,0xB2,0xA9,0xF9,0xB4,0x12,0xA2,0x63,0x89,
	0x40,0x8E,0xEA,0x62,0xEE,0x0B,0x01,0x82,0x6F,0xB3,0x5E,0x5C,0x36,0xBE,0xF4,0x97,
	0x2C,0xCF,0x96,0x7C,0x0D,0xAD,0x62,0xCE,0xD4,0x38,0xC5,0x32,0x02,0x24,0x57,0x27,
	0xE0,0xCF,0x56,0xA5,0x72,0x6D,0x90,0x89,0x2D,0x4C,0x34,0xF6,0x1D,0xDD,0x88,0x5E,
	0x7A,0x23,0xE3,0x6F,0x42,0xA3,0xD9,0x58,0x7E,0xE3,0x52,0x74,0x57,0x63,0xB7,0xB2,
	0xC1,0xA3,0x30,0x92,0x2E,0xB0,0x91,0x01,0x13,0x36,0x9A,0x6A,0xA7,0x5B,0x3C,0x07,
	0xFB,0xD8,0x1E,0x7E,0xCF,0x49,0xAB,0x3F,0xCA,0xCE,0x74,0x40,0x54,0x8D,0x83,0x61,
	0xCA,0xC3,0x76,0x59,0x5C,0x9F,0x49,0x8A,0x7D,0xD1,0x17,0x9C,0xA4,0xDB,0xB9,0x16,
	0x4D,0x64,0xF7,0xC7,0xF0,0x24,0xE7,0x00,0xB6,0x98,0xD5,0x8B,0x54,0xCB,0x1E,0x8B,
	0xA2,0x2B,0x7D,0x50,0x51,0x8A,0xF0,0xEF,0x47,0xAE,0xD0,0xD6,0xA0,0x42,0x8A,0xD8,
	0x22,0xAF,0x02,0x99,0x4A,0xE0,0x8D,0x8D,0xBF,0x11,0x05,0xA4,0xC4,0x9D,0xB3,0x89,
	0xB4,0x4C,0xC9,0xF7,0x4D,0xC5,0x2A,0x35,0x95,0x30,0xF3,0x0E,0x2F,0xEC,0x6E,0x3A,
	0x8B,0x05,0x76,0xED,0x1A,0x7C,0xC0,0xE7,0x22,0xCB,0x59,0xFF,0xE6,0x37,0x78,0x44,
	0xD4,0xEE,0xAD,0xD7,0xBD,0x2E,0xB7,0x6A,0xA4,0x4E,0x0E,0xFB,0xB0,0xF5,0xCB,0x87,
	0xCF,0xC3,0x18,0x64,0x6F,0x26,0x5C,0xD7,0x16,0xC8,0x7F,0xAB,0x29,0xC4,0xBA,0xFF,
	0xCD,0x1C,0xE4,0x3A,0xF2,0xEB,0x6A,0x38,0xE4,0x65,0xC2,0x33,0x03,0x26,0x7D,0x9B,
	0x7E,0x1D,0x83,0x00,0x04,0x2D,0x2B,0x5F,0xFE,0x39,0x7E,0xF1,0x3C,0xA2,0x8C,0x52,
	0x95,0xBF,0x46,0x81,0x24,0x44,0xF8,0x10,0xC3,0x87,0x8E,0x64,0x80,0x17,0x44,0xE2,
	0x8B,0xD1,0x3C,0x4A,0xE2,0x1F,0xA9,0xDE,0x75,0x13,0xFC,0x2E,0x86,0x0A,0x5C,0x5F,
	0x92,0x2B,0x92,0x2D,0x2A,0xEC,0xD2,0x5C,0x82,0x6B,0x76,0x1E,0xED,0xE6,0x56,0xF7,
	0xD2,0xDB,0x96,0x68,0x02,0x68,0x99,0x49,0xEE,0x88,0x66,0xCE,0x5D,0x08,0x88,0xA8,
	0xB9,0x24,0xB0,0xB4,0xDC,0xA6,0xC9,0xD8,0x68,0x80,0xBF,0x6B,0x32,0x57,0x7F,0x91,
	0x0E,0x37,0x59,0xF6,0x76,0xD2,0xC5,0x0B,0xF3,0x23,0xBF,0x38,0x52,0x0D,0x97,0x81,
	0x17,0xBB,0x9A,0xC2,0x55,0x44,0x72,0xCE,0xEE,0xFA,0xBB,0xDA,0xAB,0xB0,0x09,0xEA,
	0xDB,0xBF,0x45,0x95,0x07,0x88,0xD4,0xD2,0x0D,0x2E,0x15,0x31,0xBE,0x6A,0xF4,0xEF,
	0xA3,0x7D,0x22,0x81,0x3B,0xA8,0x83,0xF9,0x42,0xE5,0x9B,0x79,0x01,0xF5,0xDC,0x19,
	0x64,0xEB,0x47,0x67,0xAF,0xA4,0xB2,0xAE,0xF8,0xF9,0x4D,0x63,0xAD,0x54,0xE1,0x02,
	0x56,0x89,0x4E,0x0A,0xE8,0x3E,0x03,0xFA,0x33,0x61,0x58,0x80,0x64,0x55,0x3C,0x8C,
	0x2A,0x3D,0x70,0x3E,0xE5,0xC1,0xA7,0x75,0xFC,0x91,0x75,0x05,0x8C,0x6E,0x3A,0x74,
	0x10,0xF1,0x30,0xE6,0xF6,0xF7,0xAB,0x6C,0xB1,0x2B,0xF0,0x2F,0x13,0x6E,0xD4,0x0A,
	0x64,0x29,0xF8,0xBB,0xA1,0xAA,0x55,0x09,0x93,0x47,0x2F,0x8C,0x7D,0xF1,0x2D,0x81,
	0xFE,0x78,0xFC,0xEE,0x3F,0xDD,0x49,0xDC,0x0D,0x52,0x5C,0x3B,0x8F,0x08,0xB0,0xDF,
	0xDC,0xFC,0xBE,0x5F,0x3B,0x53,0x82,0xE2,0xBD,0x6D,0x5D,0xF2,0x8D,0xFB,0x5A,0x1D,
	0x15,0x1B,0xE4,0xB1,0x56,0x06,0x1A,0xF8,0x9C,0xB9,0x44,0xF2,0xD9,0xF4,0xB2,0x00,
	0x9A,0x94,0x62,0x33,0x7E,0x0A,0xB0,0x0C,0xD5,0xEF,0x8E,0xA8,0xEB,0x47,0xE9,0x20,
	0xA8,0x68,0xEF,0x53,0xA0,0x59,0x1B,0xA0,0x2B,0xC5,0x2B,0x30,0xB6,0x5D,0xAB,0xB4,
	0x5F,0x86,0x71,0x95,0x89,0xFC,0xC7,0x9A,0xC3,0xED,0x82,0xA0,0x3D,0x73,0xC1,0x36,
	0x01,0x5F,0x9E,0xD7,0xE3,0xC0,0x62,0x74,0xED,0x13,0xB6,0xD6,0xD5,0x37,0x17,0xE1,
	0x39,0xC7,0x6D,0x31,0xBA,0x02,0xAF,0xD5,0xCC,0x51,0xA8,0x09,0x3F,0x00,0x4A,0x8F,
	0xA6,0x23,0x13,0x88,0xCD,0x1F,0x38,0x60,0xE7,0xE7,0x53,0xDC,0x65,0xE8,0x53,0x26,
	0xBB,0xE1,0x1F,0x65,0xF0,0xAD,0x53,0x3B,0xBD,0xAD,0x97,0xAC,0xD1,0xA5,0xD0,0xE9,
	0xEB,0xD6,0x11,0xD5,0x00,0xDF,0x72,0x9C,0xCC,0x7F,0xD3,0x67,0xA1,0x3A,0x79,0xE1,
	0x85,0x70,0xE5,0x43,0xC9,0x28,0xA5,0x2F,0x9E,0xE7,0xFE,0xEB,0x14,0x10,0x23,0xC7,
	0xAF,0xB1,0x24,0xC8,0xE5,0x44,0x6F,0x4C,0x04,0xEC,0xC1,0xF0,0x23,0x1C,0xF6,0xAC,
	0xAF,0xC4,0x0E,0x2D,0x59,0x39,0x47,0xA9,0x9E,0xD9,0x2E,0x79,0xBA,0xFE,0x4F,0x12,
	0x7F,0x63,0x7F,0x62,0x67,0x7C,0x52,0x2F,0xCA,0x8B,0x6B,0x4F,0x10,0x8F,0x14,0xC6,
	0xA1,0x9B,0x45,0x15,0x90,0x63,0x22,0x5D,0x68,0x4B,0xCF,0xFA,0x6A,0x06,0xF0,0x26,
	0xAC,0x6C,0x3A,0x89,0x25,0xF3,0x5E,0x90,0x06,0x93,0xB6,0x35,0x0D,0x85,0x60,0x98,
	0xBC,0x6E,0xF2,0xA5,0x17,0x29,0x70,0xD6,0xFF,0x0C,0xD0,0xC0,0x35,0xD7,0x4A,0xFD };


	const uint8_t Tab1_Comp[11*8] = {
	0x70,0x49,0xD7,0xE3,0xDF,0x3C,0x96,0x03,0x2A,0x70,0x82,0xA6,0x5F,0xDE,0xCC,0x0C,
	0x2A,0x62,0x2A,0x3E,0xA4,0x0C,0x0A,0xAB,0x4F,0x06,0x5D,0xD4,0x14,0xAA,0xE1,0xC3,
	0x96,0xDA,0x16,0x36,0x45,0x3C,0x63,0xC2,0x97,0x71,0x87,0xAB,0xFA,0xB2,0xFC,0xD6,
	0x8F,0x85,0xC9,0x04,0x56,0xBA,0xEB,0x3F,0x42,0x9F,0xCB,0x66,0x55,0x45,0x1C,0x96,
	0xFF,0x4D,0x35,0xDF,0x88,0x0E,0xDC,0xC8,0x4E,0x3F,0x81,0x74,0xD8,0x77,0x4C,0x8E,
	0x00,0xC0,0x64,0x83,0x4E,0xBB,0xF0,0xB1 };

	unsigned short buff8[8];
	uint8_t buff11[11 + 1]; // +1 to avoid func2 bug
	int i1, i2;
	buff11[11] = 0;

	CommonMain_1_D2_13_15(datain, buff11);
	for (i1 = 0; i1 < 11; i1++)
	{
		buff11[i1] ^= Tab1_Comp[(loopval * 11) + i1];
	}

	for (i1 = 0; i1 < 8; i1++)
	{
		buff8[i1] = CommonMain_2_D2_13_15(buff11, i1 * 11);
	}

	for (i1 = 0; i1 < 8; i1++)
	{
		dataout[i1] = Tab0_Comp[buff8[i1]];
	}

	i1 = 1;
	while (i1 < 8)
	{
		i2 = 0 ;
		while (i2 < 8)
		{
			CommonMain_3_D2_13_15(&dataout[i2], &dataout[i1 + i2], i1);
			i2 += (i1 * 2);
		}
		i1 *= 2;
	}
}

void Common_D2_13_15(uint8_t *cw0, const uint8_t *cw1, int loopval)
{
	int i;
	uint8_t buff8[8];

	CommonMain_D2_13_15(cw1, buff8, loopval);
	for (i = 0; i < 8; i++)
	{
		cw0[i] ^= buff8[i];
	}
}

void ExchangeCWs(uint8_t *cw0, uint8_t *cw1)
{
	int i;
	uint8_t b;

	for (i = 0; i < 8; i++)
	{
		b = cw1[i];
		cw1[i] = cw0[i];
		cw0[i] = b;
	}
}


void hdSurEncPhase1_D2_13_15(uint8_t *cws)
{
	int i;

	for (i = 0; i <= 7; i++)
	{
		// Possible code
		if ((i & 1) == 0)
		{
			Common_D2_13_15((uint8_t *) &cws[0], (uint8_t *) &cws[8], i);
		}
		else
		{
			Common_D2_13_15((uint8_t *) &cws[8], (uint8_t *) &cws[0], i);
		}
	}
	ExchangeCWs((uint8_t *) &cws[0], (uint8_t *) &cws[8]);
}

void hdSurEncPhase2_D2_13_15(uint8_t *cws)
{
	int i;

	for (i = 7; i >= 0; i--)
	{
		// Possible code
		if ((i & 1) == 0)
		{
			Common_D2_13_15((uint8_t *) &cws[8], (uint8_t *) &cws[0], i);
		}
		else
		{
			Common_D2_13_15((uint8_t *) &cws[0], (uint8_t *) &cws[8], i);
		}
	}
	ExchangeCWs((uint8_t *) &cws[8], (uint8_t *) &cws[0]);
}


static int32_t viaccess_card_init(struct s_reader *reader, ATR *newatr)
{
	get_atr;
	def_resp;
	int32_t i;
	uint8_t buf[256];
	uint8_t insac[] = { 0xca, 0xac, 0x00, 0x00, 0x00 }; // select data
	uint8_t insb8[] = { 0xca, 0xb8, 0x00, 0x00, 0x00 }; // read selected data
	uint8_t insa4[] = { 0xca, 0xa4, 0x00, 0x00, 0x00 }; // select issuer
	uint8_t insc0[] = { 0xca, 0xc0, 0x00, 0x00, 0x00 }; // read data item
	static const uint8_t insFAC[] = { 0x87, 0x02, 0x00, 0x00, 0x03 }; // init FAC
	static const uint8_t FacDat[] = { 0x00, 0x00, 0x28 };
	static uint8_t ins8702_data[] = { 0x00, 0x00, 0x11};
	static uint8_t ins8704[] = { 0x87, 0x04, 0x00, 0x00, 0x07 };
	static uint8_t ins8706[] = { 0x87, 0x06, 0x00, 0x00, 0x04 };

	if((atr[1] != 0x77) || ((atr[2] != 0x18) && (atr[2] != 0x11) && (atr[2] != 0x19)) || ((atr[9] != 0x68) && (atr[9] != 0x6C) && (atr[9] != 0x64)))
	{
		return ERROR;
	}

	write_cmd(insFAC, FacDat);
	if(!(cta_res[cta_lr - 2] == 0x90 && cta_res[cta_lr - 1] == 0))
	{
		return ERROR;
	}

	if(!cs_malloc(&reader->csystem_data, sizeof(struct viaccess_data)))
	{
		return ERROR;
	}

	struct viaccess_data *csystem_data = reader->csystem_data;

	write_cmd(insFAC, ins8702_data);
	if((cta_res[cta_lr - 2] == 0x90) && (cta_res[cta_lr - 1] == 0x00))
	{
		write_cmd(ins8704, NULL);
		if((cta_res[cta_lr - 2] == 0x90) && (cta_res[cta_lr - 1] == 0x00))
		{
			write_cmd(ins8706, NULL);
			if((cta_res[cta_lr - 2] == 0x90) && (cta_res[cta_lr - 1] == 0x00))
			{
				csystem_data->last_geo.number_ecm = (cta_res[2] << 8) | (cta_res[3]);
				rdr_log(reader, "using ecm #%x for long viaccess ecm", csystem_data->last_geo.number_ecm);
			}
		}
	}

	reader->caid = 0x500;
	memset(reader->prid, 0xff, sizeof(reader->prid));
	insac[2] = 0xa4;
	write_cmd(insac, NULL); // request unique id
	insb8[4] = 0x07;
	write_cmd(insb8, NULL); // read unique id
	memcpy(reader->hexserial, cta_res + 2, 5);
	// rdr_log(reader, "[viaccess-reader] type: Viaccess, ver: %s serial: %llu", ver, b2ll(5, cta_res+2));
	rdr_log_sensitive(reader, "type: Viaccess (%sstandard atr), caid: %04X, serial: {%llu}",
			atr[9] == 0x68 ? "" : "non-", reader->caid, (unsigned long long) b2ll(5, cta_res + 2));

	i = 0;
	insa4[2] = 0x00;
	write_cmd(insa4, NULL); // select issuer 0
	buf[0] = 0;
	while((cta_res[cta_lr - 2] == 0x90) && (cta_res[cta_lr - 1] == 0))
	{
		insc0[4] = 0x1a;
		write_cmd(insc0, NULL); // show provider properties
		cta_res[2] &= 0xF0;
		reader->prid[i][0] = 0;
		memcpy(&reader->prid[i][1], cta_res, 3);
		memcpy(&csystem_data->availkeys[i][0], cta_res + 10, 16);
		snprintf((char *)buf + cs_strlen((char *)buf), sizeof(buf) - cs_strlen((char *)buf), ",%06X", b2i(3, &reader->prid[i][1]));
		// rdr_log(reader, "[viaccess-reader] buf: %s", buf);

		insac[2] = 0xa5;
		write_cmd(insac, NULL); // request sa
		insb8[4] = 0x06;
		write_cmd(insb8, NULL); // read sa
		memcpy(&reader->sa[i][0], cta_res + 2, 4);

		insa4[2] = 0x02;
		write_cmd(insa4, NULL); // select next issuer
		i++;
	}
	reader->nprov = i;
	rdr_log(reader, "providers: %d (%s)", reader->nprov, buf + 1);

	get_maturity(reader);
	if(cfg.ulparent)
	{
		unlock_parental(reader);
	}

	rdr_log(reader, "ready for requests");
	return OK;
}

bool dcw_crc(uint8_t *dw)
{
	int8_t i;
	for(i = 0; i < 16; i += 4) if(dw[i + 3] != ((dw[i] + dw[i + 1] + dw[i + 2]) & 0xFF)) { return 0; }
	return 1;
}

static int32_t viaccess_do_ecm(struct s_reader *reader, const ECM_REQUEST *er, struct s_ecm_answer *ea)
{
	def_resp;
	static const uint8_t insa4[] = { 0xca, 0xa4, 0x04, 0x00, 0x03 }; // set provider id
	uint8_t ins88[] = { 0xca, 0x88, 0x00, 0x00, 0x00 }; // set ecm
	uint8_t insf8[] = { 0xca, 0xf8, 0x00, 0x00, 0x00 }; // set geographic info
	static const uint8_t insc0[] = { 0xca, 0xc0, 0x00, 0x00, 0x12 }; // read dcw
	struct viaccess_data *csystem_data = reader->csystem_data;

	// //XXX what is the 4th uint8_t for ??
	int32_t ecm88Len = MIN(MAX_ECM_SIZE - 4, SCT_LEN(er->ecm) - 4);
	if(ecm88Len < 1)
	{
		rdr_log(reader, "ECM: Size of ECM couldn't be correctly calculated.");
		return ERROR;
	}
	uint8_t ecmData[ecm88Len];
	memset(ecmData, 0, ecm88Len);
	memcpy(ecmData, er->ecm + 4, ecm88Len);
	uint8_t *ecm88Data = &ecmData[0];
	uint32_t provid = 0;
	int32_t rc = 0;
	int32_t hasD2 = 0;
	uint8_t hasE0 = 0;
	int32_t curEcm88len = 0;
	int32_t nanoLen = 0;
	uint8_t *nextEcm;
	uint8_t DE04[MAX_ECM_SIZE];
	int32_t D2KeyID = 0;
	int32_t curnumber_ecm = 0;
	// nanoD2 d2 02 0d 02 -> D2 nano, len 2
	// 0b, 0f, 13 -> pre AES decrypt CW
	// 0d, 11, 15 -> post AES decrypt CW

	int32_t nanoD2 = 0; // knowns D2 nanos: 0x0b ,0x0d ,0x0f ,0x11, 0x13, 0x15

	memset(DE04, 0, sizeof(DE04)); //fix dorcel de04 bug

	nextEcm = ecm88Data;

	while(ecm88Len > 0 && !rc)
	{

		if(ecm88Data[0] == 0x00 &&  ecm88Data[1] == 0x00)
		{
			// nano 0x00 and len 0x00 aren't valid... something is obviously wrong with this ecm.
			rdr_log(reader, "ECM: Invalid ECM structure. Rejecting");
			return ERROR;
		}

		// 80 33 nano 80 (ecm) + len (33)
		if(ecm88Data[0] == 0x80) // nano 80, give ecm len
		{
			curEcm88len = ecm88Data[1];
			nextEcm = ecm88Data + curEcm88len + 2;
			ecm88Data += 2;
			ecm88Len -= 2;
		}

		if(!curEcm88len) // there was no nano 80 -> simple ecm
		{
			curEcm88len = ecm88Len;
		}

		// d2 02 0d 02 -> D2 nano, len 2, select the AES key to be used
		if(ecm88Data[0] == 0xd2)
		{
			// test if it needs AES decrypt
			if(ecm88Data[2] == 0x0b)
			{
				nanoD2 = 0x0b;
				rdr_log_dbg(reader, D_READER, "ECM: nano D2 0x0b");
			}

			if(ecm88Data[2] == 0x0d)
			{
				nanoD2 = 0x0d;
				rdr_log_dbg(reader, D_READER, "ECM: nano D2 0x0d");
			}

			if(ecm88Data[2] == 0x0f)
			{
				nanoD2 = 0x0f;
				rdr_log_dbg(reader, D_READER, "ECM: nano D2 0x0f");
			}

			if(ecm88Data[2] == 0x11)
			{
				nanoD2 = 0x11;
				rdr_log_dbg(reader, D_READER, "ECM: nano D2 0x11");
			}

			if(ecm88Data[2] == 0x13)
			{
				nanoD2 = 0x13;
				rdr_log_dbg(reader, D_READER, "ECM: nano D2 0x13");
			}

			if(ecm88Data[2] == 0x15)
			{
				nanoD2 = 0x15;
				rdr_log_dbg(reader, D_READER, "ECM: nano D2 0x15");
			}

			// use the d2 arguments to get the key # to be used
			int32_t len = ecm88Data[1] + 2;
			D2KeyID = ecm88Data[3];
			ecm88Data += len;
			ecm88Len -= len;
			curEcm88len -= len;
			hasD2 = 1;
		}
		else
		{
			hasD2 = 0;
		}

		// 40 07 03 0b 00 -> nano 40, len =7 ident 030B00 (tntsat), key #0 <== we're pointing here
		// 09 -> use key #9
		// 05 67 00
		if((ecm88Data[0] == 0x90 || ecm88Data[0] == 0x40) && (ecm88Data[1] == 0x03 || ecm88Data[1] == 0x07))
		{
			uint8_t ident[3], keynr;
			uint8_t *ecmf8Data = 0;
			int32_t ecmf8Len = 0;

			nanoLen = ecm88Data[1] + 2;
			keynr = ecm88Data[4] & 0x0F;

			// 40 07 03 0b 00 -> nano 40, len =7 ident 030B00 (tntsat), key #0 <== we're pointing here
			// 09 -> use key #9
			if(nanoLen > 5)
			{
				curnumber_ecm = (ecm88Data[6] << 8) | (ecm88Data[7]);
				rdr_log_dbg(reader, D_READER, "checking if the ecm number (%x) match the card one (%x)", curnumber_ecm, csystem_data->last_geo.number_ecm);
				// if we have an ecm number we check it.
				// we can't assume that if the nano len is 5 or more we have an ecm number
				// as some card don't support this
				if(csystem_data->last_geo.number_ecm > 0)
				{
					if(csystem_data->last_geo.number_ecm == curnumber_ecm && !(ecm88Data[nanoLen - 1] == 0x01))
					{
						keynr = ecm88Data[5];
						rdr_log_dbg(reader, D_READER, "keyToUse = %02x, ECM ending with %02x", ecm88Data[5], ecm88Data[nanoLen - 1]);
					}
					else
					{
						if(ecm88Data[nanoLen - 1] == 0x01)
						{
							rdr_log_dbg(reader, D_READER, "Skip ECM ending with = %02x for ecm number (%x) for provider %02x%02x%02x",
								ecm88Data[nanoLen - 1], curnumber_ecm, ecm88Data[2], ecm88Data[3], ecm88Data[4]);
						}
						rdr_log_dbg(reader, D_READER, "Skip ECM ending with = %02x for ecm number (%x)", ecm88Data[nanoLen - 1], curnumber_ecm);
						ecm88Data = nextEcm;
						ecm88Len -= curEcm88len;
						continue; // loop to next ecm
					}
				}
				else // long ecm but we don't have an ecm number so we have to try them all.
				{
					keynr = ecm88Data[5];
					rdr_log_dbg(reader, D_READER, "keyToUse = %02x", ecm88Data[5]);
				}
			}

			memcpy(ident, &ecm88Data[2], sizeof(ident));
			provid = b2i(3, ident);
			ident[2] &= 0xF0;

			if(hasD2 && reader->aes_list)
			{
				// check that we have the AES key to decode the CW
				// if not there is no need to send the ecm to the card
				if(!aes_present(reader->aes_list, 0x500, (uint32_t)(provid & 0xFFFFF0) , D2KeyID))
				{
					return ERROR;
				}
			}

			if(!chk_prov(reader, ident, keynr))
			{
				rdr_log_dbg(reader, D_READER, "ECM: provider or key not found on card");
				snprintf(ea->msglog, MSGLOGSIZE, "provider(%02x%02x%02x) or key(%d) not found on card", ident[0], ident[1], ident[2], keynr);
				return ERROR;
			}

			ecm88Data += nanoLen;
			ecm88Len -= nanoLen;
			curEcm88len -= nanoLen;

			// DE04
			if(ecm88Data[0] == 0xDE && ecm88Data[1] == 0x04)
			{
				memcpy(DE04, &ecm88Data[0], 6);
				ecm88Data += 6;
			}

			// E0 (seen so far in logs: E0020002 or E0022002, but not in all cases delivers invalid cw so just detect!)
			if(ecm88Data[0] == 0xE0 && ecm88Data[1] == 0x02)
			{
				rdr_log_dbg(reader, D_READER,"[viaccess-reader] nano E0 ECM detected!");
				hasE0=1;
			}

			if(csystem_data->last_geo.provid != provid)
			{
				csystem_data->last_geo.provid = provid;
				csystem_data->last_geo.geo_len = 0;
				csystem_data->last_geo.geo[0] = 0;
				write_cmd(insa4, ident); // set provider
			}

			// Nano D2 0x0b, 0x0f, 0x13 -> pre AES decrypt CW
			if(hasD2 && (nanoD2 == 0x0b|| nanoD2 == 0x0f|| nanoD2 == 0x13))
			{
				uint8_t *ecm88DataCW = ecm88Data;
				int32_t cwStart = 0;
				//int32_t cwStartRes = 0;
				int32_t must_exit = 0;
				// find CW start
				while(cwStart < curEcm88len - 1 && !must_exit)
				{
					if(ecm88Data[cwStart] == 0xEA && ecm88Data[cwStart + 1] == 0x10)
					{
						ecm88DataCW = ecm88DataCW + cwStart + 2;
						must_exit = 1;
					}
					cwStart++;
				}

				if(nanoD2 == 0x0f)
				{
					hdSurEncPhase1_D2_0F_11(ecm88DataCW);
					hdSurEncPhase2_D2_0F_11(ecm88DataCW);
				}

				if(nanoD2 == 0x13)
				{
					hdSurEncPhase1_D2_13_15(ecm88DataCW);
				}

				// use AES from list to decrypt CW
				rdr_log_dbg(reader, D_READER, "Decoding CW : using AES key id %d for provider %06x", D2KeyID, (provid & 0xFFFFF0));
				if(aes_decrypt_from_list(reader->aes_list, 0x500, (uint32_t)(provid & 0xFFFFF0), D2KeyID, &ecm88DataCW[0], 16) == 0)
				{
					snprintf(ea->msglog, MSGLOGSIZE, "Missing AES key(%d)[aka E%X]",D2KeyID, D2KeyID);
				}

				if(nanoD2 == 0x0f)
				{
					hdSurEncPhase1_D2_0F_11(ecm88DataCW);
				}

				if(nanoD2 == 0x13)
				{
					hdSurEncPhase2_D2_13_15(ecm88DataCW);
				}
			}

			while(ecm88Len > 1 && ecm88Data[0] < 0xA0)
			{
				nanoLen = ecm88Data[1] + 2;
				if(!ecmf8Data)
				{
					ecmf8Data = (uint8_t *)ecm88Data;
				}
				ecmf8Len += nanoLen;
				ecm88Len -= nanoLen;
				curEcm88len -= nanoLen;
				ecm88Data += nanoLen;
			}

			if(ecmf8Len)
			{
				if(csystem_data->last_geo.geo_len != ecmf8Len || memcmp(csystem_data->last_geo.geo, ecmf8Data, csystem_data->last_geo.geo_len))
				{
					memcpy(csystem_data->last_geo.geo, ecmf8Data, ecmf8Len);
					csystem_data->last_geo.geo_len = ecmf8Len;
					insf8[3] = keynr;
					insf8[4] = ecmf8Len;
					write_cmd(insf8, ecmf8Data);
				}
			}
			ins88[2] = ecmf8Len ? 1 : 0;
			ins88[3] = keynr;
			ins88[4] = (curEcm88len > 0xFF) ? 0x00 : curEcm88len;
			//
			// we should check the nano to make sure the ecm is valid
			// we should look for at least 1 E3 nano, 1 EA nano and the F0 signature nano
			//
			// DE04
			if(DE04[0] == 0xDE)
			{
				uint32_t l = curEcm88len - 6;
				if(l > MAX_ECM_SIZE || curEcm88len <= 6)    //don't known if this is ok...
				{
					rdr_log(reader, "ecm invalid/too long! len=%d", curEcm88len);
					return ERROR;
				}
				memcpy(DE04 + 6, (uint8_t *)ecm88Data, l);
				write_cmd(ins88, DE04); // request dcw
			}
			else
			{
				write_cmd(ins88, (uint8_t *)ecm88Data); // request dcw
			}

			write_cmd(insc0, NULL); // read dcw
			switch(cta_res[0])
			{
				case 0xe8: // even
					if(cta_res[1] == 8)
					{
						memcpy(ea->cw, cta_res + 2, 8);
						rc = 1;
					} break;

				case 0xe9: // odd
					if(cta_res[1] == 8)
					{
						memcpy(ea->cw + 8, cta_res + 2, 8);
						rc = 1;
					} break;

				case 0xea: // complete
					if(cta_res[1] == 16)
					{
						memcpy(ea->cw, cta_res + 2, 16);
						rc = 1;
					} break;

				default :
					ecm88Data = nextEcm;
					ecm88Len -= curEcm88len;
					rdr_log_dbg(reader, D_READER, "Error: card respondend %02X %02X, trying next ECM", cta_res[0], cta_res[1]);
					snprintf(ea->msglog, MSGLOGSIZE, "key to use is not the current one, trying next ECM");
			}
		}
		else
		{
			//ecm88Data=nextEcm;
			//ecm88Len-=curEcm88len;
			rdr_log_dbg(reader, D_READER, "ECM: Unknown ECM type");
			snprintf(ea->msglog, MSGLOGSIZE, "Unknown ECM type");
			return ERROR; /*Lets interupt the loop and exit, because we don't know this ECM type.*/
		}
	}

	// Nano D2 0d, 11, 15 -> post AES decrypt CW

	if(hasD2 && !dcw_crc(ea->cw) && (nanoD2 == 0x0d || nanoD2 == 0x11 || nanoD2 == 0x15))
	{
		if(nanoD2 == 0x11)
		{
			hdSurEncPhase1_D2_0F_11(ea->cw);
			hdSurEncPhase2_D2_0F_11(ea->cw);
		}

		if(nanoD2 == 0x15)
		{
			hdSurEncPhase1_D2_13_15(ea->cw);
		}
		rdr_log_dbg(reader, D_READER, "Decoding CW : using AES key id %d for provider %06x", D2KeyID, (provid & 0xFFFFF0));
		rc = aes_decrypt_from_list(reader->aes_list, 0x500, (uint32_t)(provid & 0xFFFFF0), D2KeyID, ea->cw, 16);

		if(rc == 0)
		{
			snprintf(ea->msglog, MSGLOGSIZE, "Missing AES key(%d)[aka E%X]",D2KeyID, D2KeyID);
		}

		if(nanoD2 == 0x11)
		{
			hdSurEncPhase1_D2_0F_11(ea->cw);
		}

		if(nanoD2 == 0x15)
		{
			hdSurEncPhase2_D2_13_15(ea->cw);
		}
	}

	if (hasE0)
	{
		if (reader->initCA28)
		{
			rdr_log_dbg(reader, D_READER, "Decrypting nano E0 encrypted cw.");
			uint8_t returnedcw[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
			memcpy(returnedcw,ea->cw,16);

			// Processing 3DES
			// Processing even cw
			des(returnedcw, reader->key_schedule1, 0); // decrypt
			des(returnedcw, reader->key_schedule2, 1); // crypt
			des(returnedcw, reader->key_schedule1, 0); // decrypt

			// Processing odd cw
			des(returnedcw + 8, reader->key_schedule1, 0); // decrypt
			des(returnedcw + 8, reader->key_schedule2, 1); // crypt
			des(returnedcw + 8, reader->key_schedule1, 0); // decrypt

			// returning value
			memcpy(ea->cw,returnedcw, 16);
		}
		else
		{
			snprintf(ea->msglog, MSGLOGSIZE, "nano E0 detected, no valid boxkey / deskey defined: no decoding");
		}
	}
	return (rc ? OK : ERROR);
}

static int32_t viaccess_get_emm_type(EMM_PACKET *ep, struct s_reader *rdr)
{
	uint32_t provid = 0;
	rdr_log_dbg(rdr, D_EMM, "Entered viaccess_get_emm_type ep->emm[0]=%02x", ep->emm[0]);

	if(ep->emm[3] == 0x90 && ep->emm[4] == 0x03)
	{
		provid = b2i(3, ep->emm + 5);
		provid &= 0xFFFFF0;
		i2b_buf(4, provid, ep->provid);
	}

	switch(ep->emm[0])
	{
		case 0x88:
			ep->type = UNIQUE;
			memset(ep->hexserial, 0, 8);
			memcpy(ep->hexserial, ep->emm + 4, 4);
			rdr_log_dbg(rdr, D_EMM, "UNIQUE");
			if(!is_network_reader(rdr))
			{
				return (!memcmp(rdr->hexserial + 1, ep->hexserial, 4)); // local reader
			}
			else
			{
				return 1; // let server decide!
			}

		case 0x8A:
		case 0x8B:
			ep->type = GLOBAL;
			rdr_log_dbg(rdr, D_EMM, "GLOBAL");
			return 1;

		case 0x8C:
		case 0x8D:
			ep->type = SHARED;
			rdr_log_dbg(rdr, D_EMM, "SHARED (part)");
			// We need those packets to pass otherwise we would never
			// be able to complete EMM reassembly
			return 1;

		case 0x8E:
			ep->type = SHARED;
			rdr_log_dbg(rdr, D_EMM, "SHARED");
			memset(ep->hexserial, 0, 8);
			memcpy(ep->hexserial, ep->emm + 3, 3);

			// local reader
			int8_t i;
			for(i = 0; i < rdr->nprov; i++)
			{
				if(!memcmp(&rdr->prid[i][2], ep->hexserial+1, 2))
				{
					return 1;
				}
				return (!memcmp(&rdr->sa[0][0], ep->hexserial, 3));
			} /* fallthrough */

		default:
			ep->type = UNKNOWN;
			rdr_log_dbg(rdr, D_EMM, "UNKNOWN");
			return 1;
	}
}

static int32_t viaccess_get_emm_filter(struct s_reader *rdr, struct s_csystem_emm_filter **emm_filters, unsigned int *filter_count)
{
	if(*emm_filters == NULL)
	{
		bool network = is_network_reader(rdr);
		int8_t device_emm = ((rdr->deviceemm > 0) ? 1 : 0); // set to 1 if device specific emms should be catched too
		const unsigned int max_filter_count = 4 + ((device_emm != 0 && rdr->nprov > 0) ? 1:0) + (3 * ((rdr->nprov > 0) ? (rdr->nprov - 1) : 0));

		if(!cs_malloc(emm_filters, max_filter_count * sizeof(struct s_csystem_emm_filter)))
		{
			return ERROR;
		}

		struct s_csystem_emm_filter *filters = *emm_filters;
		*filter_count = 0;

		int32_t idx = 0;
		int32_t prov;

		if(rdr->nprov > 0 && device_emm == 1)
		{
			filters[idx].type = EMM_GLOBAL; // 8A or 8B no reassembly needed!
			filters[idx].enabled = 1;
			filters[idx].filter[0] = 0x8A;
			filters[idx].mask[0] = 0xFE;
			filters[idx].filter[3] = 0x80; // device specific emms
			filters[idx].mask[3] = 0x80;
			idx++;
		}

		// shared are most important put them on top, define first since viaccess produces a lot of filters!
		for(prov = 0; (prov < rdr->nprov); prov++)
		{
			if((rdr->prid[prov][2] &0xF0) == 0xF0) // skip this not useful provider
			{
				continue;
			}

			filters[idx].type = EMM_SHARED; // 8C or 8D always first part of shared, second part delivered by 8E!
			filters[idx].enabled = 1;
			filters[idx].filter[0] = 0x8C;
			filters[idx].mask[0] = 0xFE;
			if(rdr->nprov > 0)
			{
				memcpy(&filters[idx].filter[4], &rdr->prid[prov][2], 2);
				filters[idx].mask[4] = 0xFF;
				filters[idx].mask[5] = 0xF0; // ignore last digit since this is key on card indicator!
			}
			idx++;

			filters[idx].type = EMM_SHARED; // 8E second part reassembly with 8c/8d needed!
			filters[idx].enabled = 1;
			filters[idx].filter[0] = 0x8E;
			filters[idx].mask[0] = 0xFF;
			if(rdr->nprov > 0)
			{
				memcpy(&filters[idx].filter[1], &rdr->sa[prov][0], 3);
				memset(&filters[idx].mask[1], 0xFF, 3);
			}
			idx++;
		}

		// globals are less important, define last since viaccess produces a lot of filters!
		for(prov = 0; (prov < rdr->nprov); prov++)
		{
			if((rdr->prid[prov][2] &0xF0) == 0xF0) // skip this not useful provider
			{
				continue;
			}

			filters[idx].type = EMM_GLOBAL; // 8A or 8B no reassembly needed!
			filters[idx].enabled = 1;
			filters[idx].filter[0] = 0x8A;
			filters[idx].mask[0] = 0xFE;
			if(rdr->nprov > 0)
			{
				memcpy(&filters[idx].filter[4], &rdr->prid[prov][2], 2);
				filters[idx].mask[4] = 0xFF;
				filters[idx].mask[5] = 0xF0; // ignore last digit since this is key on card indicator!
			}
			else if (device_emm == 0)
			{
				filters[idx].filter[3] = 0x00; // additional filter to cancel device specific emms
				filters[idx].mask[3] = 0x80;
			}
			idx++;
		}

		filters[idx].type = EMM_UNIQUE;
		filters[idx].enabled = 1;
		filters[idx].filter[0] = 0x88;
		filters[idx].mask[0] = 0xFF;
		if(!network) // network has only 3 digits out of 4
		{
			memcpy(&filters[idx].filter[1], rdr->hexserial + 1, 4);
			memset(&filters[idx].mask[1], 0xFF, 4);
		}
		else
		{
			memcpy(&filters[idx].filter[1], rdr->hexserial + 1, 3);
			memset(&filters[idx].mask[1], 0xFF, 3);
		}
		idx++;

		*filter_count = idx;
	}

	return OK;
}

static int32_t viaccess_do_emm(struct s_reader *reader, EMM_PACKET *ep)
{
	def_resp;
	static const uint8_t insa4[] = { 0xca, 0xa4, 0x04, 0x00, 0x03 }; // set provider id
	uint8_t insf0[] = { 0xca, 0xf0, 0x00, 0x01, 0x22 }; // set adf
	uint8_t insf4[] = { 0xca, 0xf4, 0x00, 0x01, 0x00 }; // set adf, encrypted
	uint8_t ins18[] = { 0xca, 0x18, 0x01, 0x01, 0x00 }; // set subscription
	uint8_t ins1c[] = { 0xca, 0x1c, 0x01, 0x01, 0x00 }; // set subscription, encrypted
	struct viaccess_data *csystem_data = reader->csystem_data;

	int32_t emmdatastart = 7;

	if (ep->emm[1] == 0x01) // emm from cccam
	{
		emmdatastart = 12;
		ep->emm[1] = 0x70; // (& 0x0f) of this byte is length, so 0x01 would increase the length by 256
		ep->emm[2] -= 1;
		if (ep->type == SHARED || ep->type == GLOBAL) // build missing 0x90 nano from provider at serial position
		{
			memcpy(ep->emm + 7, ep->emm + 3, 3);
			ep->emm[5] = 0x90;
			ep->emm[6] = 0x03;
			ep->emm[9] |= 0x01;
			ep->emm[10] = 0x9E;
			ep->emm[11] = 0x20;
			emmdatastart = 5;
		}
	}

	if(ep->type == UNIQUE)
	{
		emmdatastart++;
	}

	if(ep->type == GLOBAL && emmdatastart == 7)
	{
		emmdatastart -= 4;
	}

	int32_t emmLen = SCT_LEN(ep->emm) - emmdatastart;
	int32_t rc = 0;

	rdr_log_dump(reader, ep->emm, emmLen + emmdatastart, "RECEIVED EMM VIACCESS");

	int32_t emmUpToEnd;
	uint8_t *emmParsed = ep->emm + emmdatastart;
	int32_t provider_ok = 0;
	uint32_t emm_provid = 0;
	uint8_t keynr = 0;
	int32_t ins18Len = 0;
	uint8_t ins18Data[512];
	uint8_t insData[512];
	uint8_t *nano81Data = 0;
	uint8_t *nano91Data = 0;
	uint8_t *nano92Data = 0;
	uint8_t *nano9EData = 0;
	uint8_t *nanoF0Data = 0;
	uint8_t *nanoA9Data = 0;

	for(emmUpToEnd = emmLen; (emmParsed[1] != 0) && (emmUpToEnd > 0); emmUpToEnd -= (2 + emmParsed[1]), emmParsed += (2 + emmParsed[1]))
	{
		rdr_log_dump(reader, emmParsed, emmParsed[1] + 2, "NANO");

		if(emmParsed[0] == 0x90 && emmParsed[1] == 0x03)
		{
			/* identification of the service operator */

			uint8_t soid[3], ident[3], i;

			for(i = 0; i < 3; i++)
			{
				soid[i] = ident[i] = emmParsed[2 + i];
			}
			ident[2] &= 0xF0;
			emm_provid = b2i(3, ident);
			keynr = soid[2] & 0x0F;

			if(chk_prov(reader, ident, keynr))
			{
				provider_ok = 1;
			}
			else
			{
				rdr_log(reader, "EMM: ignored since provider or key not present on card (%x, %x)", emm_provid, keynr);
				return SKIPPED;
			}

			// check if the provider changes. If yes, set the new one. If not, don't... card will return an error if we do.
			if(csystem_data->last_geo.provid != emm_provid)
			{
				write_cmd(insa4, ident);
				if(cta_res[cta_lr - 2] != 0x90 || cta_res[cta_lr - 1] != 0x00)
				{
					rdr_log_dump(reader, insa4, 5, "set provider cmd:");
					rdr_log_dump(reader, soid, 3, "set provider data:");
					rdr_log(reader, "update error: %02X %02X", cta_res[cta_lr - 2], cta_res[cta_lr - 1]);
					return ERROR;
				}
			}
			// as we are maybe changing the used provider, clear the cache, so the next ecm will re-select the correct one
			csystem_data->last_geo.provid = 0;
			csystem_data->last_geo.geo_len = 0;
			csystem_data->last_geo.geo[0]  = 0;

		}
		else if(emmParsed[0] == 0x9e && emmParsed[1] == 0x20)
		{
			/* adf */

			if(!nano91Data)
			{
				/* adf is not crypted, so test it */

				uint8_t custwp;
				uint8_t *afd;

				custwp = reader->sa[0][3];
				afd = (uint8_t *)emmParsed + 2;

				if(afd[31 - custwp / 8] & (1 << (custwp & 7)))
				{
					rdr_log_dbg(reader, D_READER, "emm for our card %08X", b2i(4, &reader->sa[0][0]));
				}
				else
				{
					rdr_log_dbg(reader, D_READER, "emm not suitable for our card %08X", b2i(4, &reader->sa[0][0]));
					return SKIPPED;
				}
			}

			// memorize
			nano9EData = emmParsed;

		}
		else if(emmParsed[0] == 0x81)
		{
			nano81Data = emmParsed;
		}
		else if(emmParsed[0] == 0x91 && emmParsed[1] == 0x08)
		{
			nano91Data = emmParsed;
		}
		else if(emmParsed[0] == 0x92 && emmParsed[1] == 0x08)
		{
			nano92Data = emmParsed;
		}
		else if(emmParsed[0] == 0xF0 && emmParsed[1] == 0x08)
		{
			nanoF0Data = emmParsed;
		}
		else if(emmParsed[0] == 0xD8 && emmParsed[2] == 0x45)
		{
			uint8_t pos = 4 + emmParsed[3];
			char *tmpbuf;
			if(emmParsed[pos] == 0x46 && ((emmParsed[pos+1] - emmParsed[1]) == pos))
			{
				if(cs_malloc(&tmpbuf, emmParsed[pos + 1]))
				{
					cs_strncpy(tmpbuf, (char *)emmParsed + pos + 2, emmParsed[pos + 1]);
					rdr_log(reader, "Viaccess EMM-text: %s", tmpbuf);
					NULLFREE(tmpbuf);
				}
			}
		}
		else
		{
			/* other nanos */
			show_subs(reader, emmParsed);
			if(emmParsed[0] == 0xA9 && ep->type == SHARED) // check on shared (reassembled) emm if all classes are present and up to date on card: error 90 40
			{
				if(!emm_provid)
				{
					rdr_log(reader, "no provid in shared emm -> skipped!");
					return SKIPPED;
				}
				int8_t match = add_find_class(reader, emm_provid, emmParsed + 2, emmParsed[1], 0);

				if(match == -2)
				{
					rdr_log(reader, "shared emm provid %06X all classes have entitlementdate already same or newer -> skipped!", emm_provid);
					return SKIPPED;
				}
				nanoA9Data = emmParsed;
			}

			memcpy(ins18Data + ins18Len, emmParsed, emmParsed[1] + 2);
			ins18Len += emmParsed [1] + 2;
		}
	}

	if(!provider_ok)
	{
		rdr_log_dbg(reader, D_READER, "provider not found in emm, continue anyway");
		// force key to 1...
		keynr = 1;
		///return ERROR;
	}

	if(!nanoF0Data)
	{
		rdr_log_dump(reader, ep->emm, ep->emmlen, "can't find 0xf0 in emm...");
		return ERROR; // error
	}

	if(nano9EData)
	{
		if(!nano91Data)
		{
			// set adf
			insf0[3] = keynr; // key
			insf0[4] = nano9EData[1] + 2;
			write_cmd(insf0, nano9EData);
			if(cta_res[cta_lr - 2] != 0x90 || cta_res[cta_lr - 1] != 0x00)
			{
				rdr_log_dump(reader, insf0, 5, "set adf cmd:");
				rdr_log_dump(reader, nano9EData, insf0[4] , "set adf data:");
				rdr_log(reader, "update error: %02X %02X", cta_res[cta_lr - 2], cta_res[cta_lr - 1]);
				return ERROR;
			}
		}
		else
		{
			// set adf crypte
			insf4[3] = keynr; // key
			insf4[4] = nano91Data[1] + 2 + nano9EData[1] + 2;
			memcpy(insData, nano91Data, nano91Data[1] + 2);
			memcpy(insData + nano91Data[1] + 2, nano9EData, nano9EData[1] + 2);
			write_cmd(insf4, insData);

			if((cta_res[cta_lr - 2] != 0x90 && cta_res[cta_lr - 2] != 0x91) || cta_res[cta_lr - 1] != 0x00)
			{
				rdr_log_dump(reader, insf4, 5, "set adf encrypted cmd:");
				rdr_log_dump(reader, insData, insf4[4], "set adf encrypted data:");
				rdr_log(reader, "update error: %02X %02X", cta_res[cta_lr - 2], cta_res[cta_lr - 1]);
				return ERROR;
			}
		}
	}

	if(!nano92Data)
	{
		// send subscription
		ins18[2] = nano9EData ? 0x01 : 0x00; // found 9E nano ?
		ins18[3] = keynr; // key
		ins18[4] = ins18Len + nanoF0Data[1] + 2;
		memcpy(insData, ins18Data, ins18Len);
		memcpy(insData + ins18Len, nanoF0Data, nanoF0Data[1] + 2);
		write_cmd(ins18, insData);

		if((cta_res[cta_lr - 2] == 0x90 || cta_res[cta_lr - 2] == 0x91) && cta_res[cta_lr - 1] == 0x00)
		{
			if(nanoA9Data)
			{
				add_find_class(reader, emm_provid, nanoA9Data + 2, nanoA9Data[1], 1);
				rdr_log(reader, "Your subscription data was updated.");
			}
			rc = 1; // written
		}
		else
		{
			rdr_log_dump(reader, ins18, 5, "set subscription cmd:");
			rdr_log_dump(reader, insData, ins18[4], "set subscription data:");

			if(!(cta_res[cta_lr -2] == 0x90 && cta_res[cta_lr - 1] == 0x40)) // dont throw softerror 9040 in log!
			{
				rdr_log(reader, "update error: %02X %02X", cta_res[cta_lr - 2], cta_res[cta_lr - 1]);
			}
			else
			{
				rc = 2; // skipped
			}
		}

	}
	else
	{
		// send subscription encrypted

		if(!nano81Data)
		{
			rdr_log_dump(reader, ep->emm, ep->emmlen, "0x92 found, but can't find 0x81 in emm...");
			return ERROR; // error
		}

		ins1c[2] = nano9EData ? 0x01 : 0x00; // found 9E nano ?
		if(ep->type == UNIQUE)
		{
			ins1c[2] = 0x02;
		}

		ins1c[3] = keynr; // key
		ins1c[4] = nano92Data[1] + 2 + nano81Data[1] + 2 + nanoF0Data[1] + 2;
		memcpy(insData, nano92Data, nano92Data[1] + 2);
		memcpy(insData + nano92Data[1] + 2, nano81Data, nano81Data[1] + 2);
		memcpy(insData + nano92Data[1] + 2 + nano81Data[1] + 2, nanoF0Data, nanoF0Data[1] + 2);
		write_cmd(ins1c, insData);

		if((cta_res[cta_lr - 2] == 0x90 || cta_res[cta_lr - 2] == 0x91) && (cta_res[cta_lr - 1] == 0x00 || cta_res[cta_lr - 1] == 0x08))
		{
			rdr_log(reader, "update successfully written");
		}
		rc = 1;
	}

	return rc;
}

static int32_t viaccess_card_info(struct s_reader *reader)
{
	def_resp;
	int32_t i, l;
	time_t now;
	struct tm timeinfo;
	uint16_t tmpdate;
	uint8_t insac[] = { 0xca, 0xac, 0x00, 0x00, 0x00 }; // select data
	uint8_t insb8[] = { 0xca, 0xb8, 0x00, 0x00, 0x00 }; // read selected data
	uint8_t insa4[] = { 0xca, 0xa4, 0x00, 0x00, 0x00 }; // select issuer
	uint8_t insc0[] = { 0xca, 0xc0, 0x00, 0x00, 0x00 }; // read data item
	static const uint8_t ins24[] = { 0xca, 0x24, 0x00, 0x00, 0x09 }; // set pin

	uint8_t cls[] = { 0x00, 0x21, 0xff, 0x9f};
	static const uint8_t pin[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04};
	struct viaccess_data *csystem_data = reader->csystem_data;

	csystem_data->last_geo.provid = 0;
	csystem_data->last_geo.geo_len = 0;
	csystem_data->last_geo.geo[0] = 0;

	rdr_log(reader, "card detected");

	cs_clear_entitlement(reader); //reset the entitlements

	// set pin
	write_cmd(ins24, pin);

	insac[2] = 0xa4;
	write_cmd(insac, NULL); // request unique id
	insb8[4] = 0x07;
	write_cmd(insb8, NULL); // read unique id
	rdr_log_sensitive(reader, "serial: {%llu}", (unsigned long long) b2ll(5, cta_res + 2));

	insa4[2] = 0x00;
	write_cmd(insa4, NULL); // select issuer 0

	for(i = 1; (cta_res[cta_lr - 2] == 0x90) && (cta_res[cta_lr - 1] == 0); i++)
	{
		bool added = false;
		uint32_t l_provid, l_sa;
		uint8_t l_name[64];
		insc0[4] = 0x1a;
		write_cmd(insc0, NULL); // show provider properties
		cta_res[2] &= 0xF0;
		l_provid = b2i(3, cta_res);

		insac[2] = 0xa5;
		write_cmd(insac, NULL); // request sa
		insb8[4] = 0x06;
		write_cmd(insb8, NULL); // read sa
		l_sa = b2i(4, cta_res + 2);

		insac[2] = 0xa7;
		write_cmd(insac, NULL); // request name
		insb8[4] = 0x02;
		write_cmd(insb8, NULL); // read name nano + len
		l = cta_res[1];
		insb8[4] = l;
		write_cmd(insb8, NULL); // read name
		cta_res[l] = 0;
		trim((char *)cta_res);

		if(cta_res[0])
		{
			snprintf((char *)l_name, sizeof(l_name), ", name: %.55s", cta_res);
		}
		else
		{
			l_name[0] = 0;
		}

		// read GEO
		insac[2] = 0xa6;
		write_cmd(insac, NULL); // request GEO
		insb8[4] = 0x02;
		write_cmd(insb8, NULL); // read GEO nano + len
		l = cta_res[1];
		char tmp[l * 3 + 1];
		insb8[4] = l;
		write_cmd(insb8, NULL); // read geo
		rdr_log_sensitive(reader, "provider: %d, id: {%06X%s}, sa: {%08X}, geo: %s", i, l_provid, l_name, l_sa, (l < 4) ? "empty" : cs_hexdump(1, cta_res, l, tmp, sizeof(tmp)));

		// read classes subscription
		insac[2] = 0xa9;
		insac[4] = 4;
		if(!reader->read_old_classes)
		{
			now = time(NULL) - (24*60*60);
			cs_gmtime_r(&now, &timeinfo);
			tmpdate = timeinfo.tm_mday | ((timeinfo.tm_mon + 1) << 5) | ((timeinfo.tm_year - 80) << 9);
			cls[0] = tmpdate >> 8;
			cls[1] = tmpdate & 0xff;
		}
		write_cmd(insac, cls); // request class subs

		while((cta_res[cta_lr - 2] == 0x90) && (cta_res[cta_lr - 1] == 0))
		{
			insb8[4] = 0x02;
			write_cmd(insb8, NULL); // read class subs nano + len
			if((cta_res[cta_lr - 2] == 0x90) && (cta_res[cta_lr - 1] == 0))
			{
				l = cta_res[1];
				insb8[4] = l;
				write_cmd(insb8, NULL); // read class subs
				if((cta_res[cta_lr - 2] == 0x90) && (cta_res[cta_lr - 1] == 0x00 || cta_res[cta_lr - 1] == 0x08))
				{
					show_class(reader, NULL, l_provid, cta_res, cta_lr - 2);
					added = true;
				}
			}
		}

		if(!added)
		{
			// add entitlement info for provid without class
			cs_add_entitlement(reader, reader->caid, l_provid, 0, 0, 0, 0, 5, 1);
		}

		insac[4] = 0;
		insa4[2] = 0x02;
		write_cmd(insa4, NULL); // select next provider
	}
	//return ERROR;
	// Start process init CA 28
	reader->initCA28=0;
	int32_t lenboxkey = reader->boxkey_length;
	int32_t lendeskey = reader->des_key_length;
	if ((lenboxkey >= 4) && (lendeskey > 0))
	{
		uint8_t ins28[] = { 0xCA, 0x28, 0x00, 0x00, 0x04 }; //Init for nanoE0 ca28
		ins28[4] = (uint8_t) lenboxkey;
		uint8_t ins28_data[4];
		memcpy(ins28_data, reader->boxkey, 4);
		write_cmd(ins28, ins28_data); // unlock card to reply on E002xxyy

		if((cta_res[cta_lr - 2] == 0x90) && (cta_res[cta_lr - 1] == 0))
		{
			rdr_log(reader, "CA 28 initialisation successful!");
			// init 3DES key
			des_set_key(reader->des_key, reader->key_schedule1);
			des_set_key(reader->des_key+8, reader->key_schedule2);
			reader->initCA28=1;
		}
		else
		{
			rdr_log(reader, "CA 28 initialisation failed! CA 28 refused");
		}
	}
	//end process init CA 28
	return OK;
}

static int32_t viaccess_reassemble_emm(struct s_reader *rdr, struct s_client *client, EMM_PACKET *ep)
{
	uint8_t *buffer = ep->emm;
	int16_t *len = &ep->emmlen;
	int32_t pos = 0, i;
	int16_t k;
	int32_t prov, provid = 0;
	struct emm_rass *r_emm = NULL;

	// Viaccess
	if(*len > 500)
	{
		return 0;
	}

	switch(buffer[0])
	{
		case 0x8c:
		case 0x8d:
			// emm-s part 1
			provid = b2i(3, ep->emm + 5); // extract provid from emm
			provid &= 0xFFFFF0; // last digit is dont care
			r_emm = find_rabuf(client, provid, (uint8_t) buffer[0], 1);

			if(!r_emm)
			{
				cs_log("[viaccess] ERROR: Can't allocate EMM reassembly buffer.");
				return 0;
			}

			if(!memcmp(&r_emm->emm, &buffer[0], *len)) // skip same shared emm, this make sure emmlen isnt replaced. emmlen = 0 means this shared emm has been used for reassembly
			{
				return 0;
			}

			memset(&r_emm->emm[0], 0, sizeof(r_emm->emm)); // zero it!
			memcpy(&r_emm->emm[0], &buffer[0], *len); // put the fresh new shared emm
			r_emm->emmlen = *len; // put the emmlen indicating that this shared emm isnt being reassembled
			rdr_log_dump_dbg(rdr, D_EMM, r_emm->emm, r_emm->emmlen, "%s: received fresh emm-gh for provid %06X", __func__, provid);
			return 0;

		case 0x8e:
			// emm-s part 2
			for(prov = 0; prov < rdr->nprov ; prov++)
			{
				if(!memcmp(&buffer[3], &rdr->sa[prov][0], 3))
				{
					//matching sa found!
					if(is_network_reader(rdr))
					{
						provid = b2i(4, ep->provid); // use provid from emm since we have nothing better!
						provid &= 0xFFFFF0; // last digit is dont care
					}

					else
					{
						provid = b2i(4, rdr->prid[prov]); // get corresponding provid from reader since there is no provid in emm payload!
						provid &= 0xFFFFF0; // last digit is dont care
					}
					r_emm = find_rabuf(client, provid, 0, 0); // nano = don't care, the shared 8c or 8d not been written gets returned!

					if(!r_emm || !r_emm->emmlen)
					{
						continue; // match but no emm-gh found for this provider
					}
					else
					{
						break; // stop searching-> emm-gh found!
					}
				}
			}

			if(!r_emm || !r_emm->emmlen)
			{
				return 0; // stop -> no emm-gh found!
			}
			//extract nanos from emm-gh and emm-s
			uint8_t emmbuf[512];

			rdr_log_dbg(rdr, D_EMM, "%s: start extracting nanos", __func__);
			//extract from emm-gh
			for(i = 3; i < r_emm->emmlen; i += r_emm->emm[i + 1] + 2)
			{
				//copy nano (length determined by i+1)
				memcpy(emmbuf + pos, r_emm->emm + i, r_emm->emm[i + 1] + 2);
				pos += r_emm->emm[i + 1] + 2;
			}

			if(buffer[2] == 0x2c)
			{
				//add 9E 20 nano + first 32 uint8_ts of emm content
				memcpy(emmbuf + pos, "\x9E\x20", 2);
				memcpy(emmbuf + pos + 2, buffer + 7, 32);
				pos += 34;

				//add F0 08 nano + 8 subsequent uint8_ts of emm content
				memcpy(emmbuf + pos, "\xF0\x08", 2);
				memcpy(emmbuf + pos + 2, buffer + 39, 8);
				pos += 10;
			}
			else
			{
				//extract from variable emm-s
				for(k = 7; k < (*len); k += buffer[k + 1] + 2)
				{
					//copy nano (length determined by k+1)
					memcpy(emmbuf + pos, buffer + k, buffer[k + 1] + 2);
					pos += buffer[k + 1] + 2;
				}
			}

			rdr_log_dump_dbg(rdr, D_EMM, buffer, *len, "%s: %s emm-s", __func__, (buffer[2] == 0x2c) ? "fixed" : "variable");

			emm_sort_nanos(buffer + 7, emmbuf, pos);
			pos += 7;

			//calculate emm length and set it on position 2
			buffer[2] = pos - 3;

			rdr_log_dump_dbg(rdr, D_EMM, r_emm->emm, r_emm->emmlen, "%s: emm-gh provid %06X", __func__, provid);
			rdr_log_dump_dbg(rdr, D_EMM, buffer, pos, "%s: assembled emm", __func__);

			*len = pos;
			r_emm->emmlen = 0; // mark this shared 8c or 8d as being used for reassembly and send to reader!
			break;
	}
	return 1;
}

const struct s_cardsystem reader_viaccess =
{
	.desc              = "viaccess",
	.caids             = (uint16_t[]){ 0x05, 0 },
	.do_emm_reassembly = viaccess_reassemble_emm,
	.do_emm            = viaccess_do_emm,
	.do_ecm            = viaccess_do_ecm,
	.card_info         = viaccess_card_info,
	.card_init         = viaccess_card_init,
	.get_emm_type      = viaccess_get_emm_type,
	.get_emm_filter    = viaccess_get_emm_filter,
};

#endif
